【问题标题】:Function implementation in constrained protocol extension is not invoked不调用约束协议扩展中的函数实现
【发布时间】:2018-06-30 00:29:40
【问题描述】:

问题总结

我有一个通用视图子类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)

【问题讨论】:

    标签: ios swift generics constraints


    【解决方案1】:

    问题在于 TintableView 只有一种特化,而且它是基于它从自己的定义中所知道的。在编译该类时,它会考虑computeTintState(),看到此时TintState 并没有被承诺完全是ControlState,因此在更通用的版本中进行编译。

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

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

    【讨论】:

    • 感谢您的回复。它当然是在试图变得神奇:) 编译器在语义上允许它有点令人不安。我天真地期望重新编译会在模块间发生,以支持受约束的行为;并且编译器在尝试从模型外部扩展具有约束的类型时抛出错误。我设法在另一个基于 Sundell 示例的案例中重现了它,所以我正在写一个摘要,并会在最后看到提交票证是否有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    相关资源
    最近更新 更多