简体   繁体   English

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

[英]Function implementation in constrained protocol extension is not invoked

Problem Summary 问题总结

I have a generic view subclass TintableView<T>: UIView , which implements a protocol TintStateComputing with identical associated type T . 我有一个通用的视图子类TintableView<T>: UIViewTintStateComputing实现了具有相同关联类型T的协议TintStateComputing TintStateComputing 's constrained extension implementation is not being called; TintStateComputing的约束扩展实现未调用; its unconstrained extension implementation is called instead. 而是调用其不受限制的扩展实现。

The TintStateComputing protocol has a function computeTintState() -> T , which does what it sounds like: checks the local state properties, and returns the according instance of T . TintStateComputing协议具有一个函数computeTintState() -> T ,其功能听起来像:检查本地状态属性,并返回T的相应实例。

I want to write extension implementations of func computeTintState() -> T on TintStateComputing , constrained on the type of T . 我想在TintStateComputingTintStateComputing func computeTintState() -> T扩展实现,受T类型的限制。 For example, with a ControlState enum: 例如,使用ControlState枚举:

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

However, in order to complete the protocol conformance, I think I need to account for other values of T . 但是,为了完成协议的一致性,我认为我需要考虑T其他值。 So I've also stated an unconstrained extension to TintStateComputing . 因此,我TintStateComputing了无限制的扩展。 This unconstrained extension implementation is always being called, instead of the constrained implementation. 总是调用此不受约束的扩展实现,而不是受约束的实现。

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

Playground Testbed 游乐场试验台

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")

Workaround 解决方法

I realized this morning that since (at least for now) what I'm really trying to accomplish is handle the isEnabled: Bool flag on the view, I could follow the same pattern as used for Defaultable to define a default 'enabled' case. 今天早上我意识到,由于(至少到目前为止)我真正想完成的工作是处理视图上的isEnabled: Bool标志,因此我可以遵循与Defaultable相同的模式来定义默认的“ enabled”情况。

public protocol Enableable {
    static var defaultEnabled: Self { get }
}

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

At that point, I can really eliminate the TintStateComputing protocol, and update my view's tintState: T implementation to account for the flag directly. 到那时,我真的可以消除TintStateComputing协议,并更新视图的tintState: T实现以直接考虑该标志。

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

It's not as generalized as putting the implementation in a constrained extension, but it will work for now. 它不像将实现放在受约束的扩展中那样普遍,但是它现在可以使用。 I think that if I have future subclasses with multi-dimensional tint-state types (eg 'enabled' + 'in-range') I'll be able to address via override . 我认为,如果将来有带有多维着色状态类型的子类(例如'enabled'+'in-range'),我将能够通过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)

The problem is that there is only one specialization of TintableView, and it's based on what it knows from its own definition. 问题在于TintableView只有一种专业化,它是基于它自己的定义所知道的。 When compiling the class, it considers computeTintState() , sees that TintState is not promised at that point to be exactly ControlState , and so compiles-in the more general version. 在编译该类时,它考虑了computeTintState() ,发现TintState并未保证TintState恰好是ControlState ,因此可以在更通用的版本中进行编译。

In order to do what you want, when it encounters TintableView<ControlState> it would need to completely reconsider and recompile the TintableView class. 为了执行您想要的操作,当遇到TintableView<ControlState> ,它将需要完全重新考虑并重新编译TintableView类。 Swift doesn't currently do that. Swift目前不这样做。 In this case, I don't consider that a bug. 在这种情况下,我不认为这是一个错误。 I think this code is trying to be too magical and is abusing extensions, but that's just my opinion on it. 我认为这段代码试图变得太神奇了,并且正在滥用扩展,但这只是我的看法。 If you believe Swift should handle this kind of case then I recommend opening a defect at bugs.swift.org. 如果您认为Swift应该处理这种情况,那么我建议在bugs.swift.org中打开一个缺陷。

Keep in mind what would happen if TintableView were in one module and the TintState == ControlState extension were in another (say in the module with the let a = ). 请记住,如果会发生什么TintableView是一个模块和TintState == ControlState扩展是在另一个(与模块中说let a = )。 In that case, it would be impossible to get the behavior you're asking for, because one module can't respecialize another module (it may not have the source code available). 在这种情况下,将不可能获得您所要求的行为,因为一个模块无法重新专门化另一个模块(它可能没有可用的源代码)。 Would you consider this code good if it behaved one way when these were in one module, but have different visible behaviors if they were in different modules? 如果将这些代码放在一个模块中时以一种方式运行,但是如果它们在不同模块中时具有不同的可见行为,那么您认为此代码很好吗? That's why I consider this too tricky and bug-prone. 这就是为什么我认为这太棘手且容易出错。 This split-module specialization problem happens all the time, but it mostly just impacts performance (and stdlib uses private compiler directives to improve that because it's a special case). 这个拆分模块专业化问题一直在发生,但是它只会影响性能(并且stdlib使用专用编译器指令来改进它,因为这是特例)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在协议扩展中将nil传递给具有可选的一般约束参数的函数 - Passing nil to a function with an optional generically constrained parameter in a protocol extension 在协议扩展VS Struct中添加协议的函数实现,该扩展符合协议并实现函数 - Add function implementation of a protocol in its extension VS Struct that conforms the protocol and implements function 覆盖协议扩展默认实现 - override protocol extension default implementation 协议扩展,变异函数 - Protocol Extension, Mutating Function 使用Swift扩展的协议方法的默认实现 - Default implementation of protocol method with Swift extension Swift:在协议扩展的静态变量中存储协议​​的实现类型 - Swift: Store Type of implementation of Protocol in static var of protocol extension 如果带有实现的扩展如下,我应该定义协议内部的计算属性吗? - Should I define computed property of inside of protocol if the extension with implementation follows? 调用协议扩展中的method而不是View Controller中的方法实现 - method in protocol extension gets called instead of method implementation in View Controller 向下转换为受关联类型约束的协议 - Downcasting to Protocol constrained by associated type 在Swift中使用扩展的协议的类函数? - Class Function to a Protocol using Extension in Swift?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM