[英]Function implementation in constrained protocol extension is not invoked
I have a generic view subclass TintableView<T>: UIView
, which implements a protocol TintStateComputing
with identical associated type T
. 我有一个通用的视图子类
TintableView<T>: UIView
, TintStateComputing
实现了具有相同关联类型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
. 我想在
TintStateComputing
上TintStateComputing
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
}
}
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")
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.