不调用受约束协议扩展中的函数实现



问题摘要

我有一个通用视图子类TintableView<T>: UIView,它实现了具有相同关联类型T的协议TintStateComputingTintStateComputing的约束扩展实现未被调用;而是调用其不受约束的扩展实现。

TintStateComputing协议有一个函数computeTintState() -> T,它的作用就像它听起来一样:检查本地状态属性,并返回T的相应实例。

我想在TintStateComputing上编写func computeTintState() -> T的扩展实现,受T类型的约束。 例如,使用ControlState枚举:

extension TintStateComputing where T == ControlState {
func computeTintState() -> T {
return self.isEnabled ? .enabled : T.default
}
}

但是,为了完成协议一致性,我认为我需要考虑T的其他值。 所以我陈述了对TintStateComputing的无约束扩展。始终调用此不受约束的扩展实现,而不是受约束的实现。

extension TintStateComputing {
func computeTintState() -> T {
return _tintState ?? T.default
}
}

游乐场试验台

import UIKit
// MARK: - Types
public enum ControlState: Int {
case normal, enabled, highlighted, selected, disabled
}
public protocol Defaultable {
static var `default`: Self { get }
}
extension ControlState: Defaultable {
public static let `default`: ControlState = .normal
}
// MARK: - TintStateComputing declaration
public protocol TintStateComputing {
associatedtype TintState: Hashable & Defaultable
func computeTintState() -> TintState
var _tintState: TintState? { get }
var isEnabled: Bool { get }
}
// MARK: - TintableView declaration
class TintableView<T: Hashable & Defaultable>: UIView, TintStateComputing {
// `typealias TintState = T` is implictly supplied by compiler
var _tintState: T?
var isEnabled: Bool = true { didSet { _tintState = nil }}
var tintState: T  {
get {
guard _tintState == nil else {
return _tintState!
}
return computeTintState()
}
set {
_tintState = newValue
}
}
}
// MARK: - Unconstrained TintStateComputing extension
extension TintStateComputing {
func computeTintState() -> TintState {
return _tintState ?? TintState.default
}
}
// MARK: - Constrained TintStateComputing extension
extension TintStateComputing where TintState == ControlState {
func computeTintState() -> TintState {
return self.isEnabled ? .enabled : TintState.default
}
}
// MARK: - Test Case
let a = TintableView<ControlState>()
a.isEnabled = true
print("Computed tint state: (a.tintState);  should be .enabled") // .normal
print("finished")

解决方法

我今天早上意识到,由于(至少目前(我真正想要完成的是处理视图上的isEnabled: Bool标志,我可以遵循与Defaultable相同的模式来定义默认的"启用"情况。

public protocol Enableable {
static var defaultEnabled: Self { get }
}
extension ControlState: Defaultable, Enableable {
public static let `default`: ControlState = .normal
public static let defaultEnabled: ControlState = .enabled
}

此时,我可以真正消除TintStateComputing协议,并更新视图的tintState: T实现以直接考虑标志。

var tintState: T  {
get {
guard _tintState == nil else { return _tintState! }
return self.isEnabled ? T.defaultEnabled : T.default
}
set {
_tintState = newValue
}
}

它不像将实现放在受约束的扩展中那样通用,但它现在可以工作。 我认为,如果我将来有具有多维色调状态类型的子类(例如"启用"+"范围内"(,我将能够通过override解决。

struct CalendarState: Equatable, Hashable, Defaultable, Enableable  {
let x: Int
static let `default`: CalendarState = CalendarState(x: 0)
static let defaultEnabled: CalendarState = CalendarState(x: 1)
}
class ControlTintableView: TintableView<ControlState> {}
class CalendarView: TintableView<CalendarState> {}
let a = ControlTintableView()
a.isEnabled = true
print("ControlTintableView computed tint state: (a.tintState);  should be: .enabled") // .enabled
let b = CalendarView()
b.isEnabled = true
print("CalendarView computed tint state: (b.tintState);  should be: CalendarState(x: 1)") // CalendarState(x: 1)

问题是TintableView只有一个专业化,它是基于它从自己的定义中知道的。当编译类时,它考虑computeTintState(),看到TintState在这一点上没有被承诺为完全ControlState,所以编译在更通用的版本。

为了做你想做的事情,当它遇到TintableView<ControlState>时,它需要完全重新考虑并重新编译TintableView类。 Swift 目前没有这样做。在这种情况下,我不认为这是一个错误。我认为这段代码试图太神奇并且滥用了扩展,但这只是我对它的看法。如果你认为 Swift 应该处理这类情况,那么我建议在 bugs.swift.org 处打开一个缺陷。

请记住,如果TintableView在一个模块中而TintState == ControlState扩展名在另一个模块中(例如在带有let a =的模块中(,会发生什么。在这种情况下,不可能获得您要求的行为,因为一个模块无法重新专用化另一个模块(它可能没有可用的源代码(。如果当它们在一个模块中时,它以一种方式运行,但如果它们在不同的模块中,则具有不同的可见行为,您会认为此代码很好吗?这就是为什么我认为这太棘手且容易出错的原因。这种拆分模块专用化问题一直在发生,但它主要只是影响性能(stdlib 使用私有编译器指令来改进这一点,因为它是一种特殊情况(。

最新更新