如何在初始化程序中为受约束的泛型获取动态调度

How to get dynamic dispatch for constrained generics in initializer

下面的代码工作得很好:

protocol VariableType {
    
    associatedtype T
    
    var value : T { get }
 
}

class UserDefaultsVariable<T> : VariableType {
   
    let storageKey : String
    
    var value : T {
        
        get {
            return UserDefaults.standard.object(forKey: storageKey) as! T
        }
        
        set(value) {
            UserDefaults.standard.set(value, forKey: storageKey)
        }
    }
    
    init( storageKey : String, initialValue : T  ) {
        self.storageKey = storageKey
        // I want dynamic dispatch here so that if T: RawRepresentable then the
        // function in the extension is called
        self.registerDefaultValue(initialValue: initialValue)
    }
    
    func registerDefaultValue( initialValue : T ) {
        debugPrint("this is called both times!")
        UserDefaults.standard.register(defaults: [storageKey : initialValue])
    }
    
}

通话中:

let simpleDefault = UserDefaultsVariable<Int>(storageKey: "simple", initialValue: 0)
let initialValue = simpleDefault.value

结果如预期的那样初始值为 0。

当我尝试扩展它以支持 RawRepresentable 类型时出现问题:

extension UserDefaultsVariable where T: RawRepresentable {
    
    var value: T {
        get {
            let rawValue = UserDefaults.standard.object(forKey: storageKey) as! T.RawValue
            return T(rawValue: rawValue)!
        }
        set {
            UserDefaults.standard.set(newValue.rawValue, forKey: storageKey)
        }
    }
    
    func registerDefaultValue( initialValue : T ) {
        debugPrint("this is never called!")
        UserDefaults.standard.register(defaults: [storageKey:initialValue.rawValue])
    }
}

当我调用这个时:

let simpleDefault = UserDefaultsVariable<Int>(storageKey: "simple", initialValue: 0)
let initialValue = simpleDefault.value

enum Direction : Int {
    case left = 0
    case right = 1
}

let enumedDefault = UserDefaultsVariable<Direction>(storageKey: "enumed", initialValue: .left)

代码崩溃是因为调用了 UserDefaultsVariable<T> 中的 registerDefaults 实现而不是专门的 UserDefaultsVariable<T:RawRepresentable> 实现。

从初始化器外部进行调用,例如

enumedDefault.registerDefaultValue(initialValue: .left)

调用正确的实现!?所以看起来调度是 通常 动态的,但不是在初始化器内部?或者可能是整个 class?

非常感谢任何帮助。

解决方案

正如@matt 指出的那样,我错误地期望 Swift 通过某种形式的多态性调用函数的受限版本,但编译器在编译时解析调用并且不尝试寻找“the最具体的实现"..

@Asperi 提出了一个可行的解决方案,但缺点是 DefaultsVariable 可以在没有初始值的情况下进行初始化,这是我试图避免的事情。

我最终做的是通过简单地 subclassing UserDefaultsVariable:

来引入多态性
class RawRepresentableUserDefaultsVariable<T: RawRepresentable> : UserDefaultsVariable<T>{
    
    override var value: T {
        get {
            let rawValue = UserDefaults.standard.object(forKey: storageKey) as! T.RawValue
            return T(rawValue: rawValue)!
        }
        set {
            UserDefaults.standard.set(newValue.rawValue, forKey: storageKey)
        }
    }
    
    override func registerDefaultValue( initialValue : T ) {
        UserDefaults.standard.register(defaults: [storageKey:initialValue.rawValue])
    }
}

let enumedDefault = RawRepresentableUserDefaultsVariable<Direction>(storageKey: "enumed", initialValue: .left)

let initialEnumValue = enumedDefault.value

这可以很好地编译并从 superclass.

中的初始化程序调用 subclass 中的 registerDefaultValue

非常感谢您的帮助。

我认为您遇到的概念性问题是实际上这个故事中的任何地方都没有“动态调度”。动态调度是一种涉及多态性的运行时机制,您的代码包含none。泛型在编译时完全解析:将发生的事情明确写入代码。

要理解我的意思,只需执行以下操作:将所有 UserDefaultsVariable<T>(...) 更改为 UserDefaultsVariable<T>.init(...) 以给自己一些可以点击的内容,然后只需开始 Control-Command-点击条款。您将看到我们跳转到编译器如何路由每个调用的位置。

enumedDefault 键入 作为 UserDefaultsVariable<Direction> 显式 就是这样输入的。所以我们(皇家编译器“我们”)知道要调用什么 registerDefaultValue 。没有“派遣”。但是没有(也不可能有)任何为 RawRepresentable 扩展定义的特殊指定 init,并且 self 在初始化程序中只是 self。因此,初始化器不会根据泛型参数化类型是否符合 RawRepresentable 的某种运行时测试神奇地调用另一个 registerDefaultValue

这是可能的方法。

这个想法是为了让初始化程序在扩展中更方便,所以泛型专用扩展与默认扩展重叠。

测试并适用于 Xcode 11.4 / swift 5.2

class UserDefaultsVariable<T> : VariableType {

    let storageKey : String

    init(storageKey: String) {
        self.storageKey = storageKey
    }

    var value : T {

        get {
            return UserDefaults.standard.object(forKey: storageKey) as! T
        }

        set(value) {
            UserDefaults.standard.set(value, forKey: storageKey)
        }
    }

    func registerDefaultValue( initialValue : T ) {
        UserDefaults.standard.register(defaults: [storageKey : initialValue])
    }

}

extension UserDefaultsVariable {

    // this initializer uses default T
    convenience init(storageKey : String, initialValue : T) {

        self.init(storageKey: storageKey)
        self.registerDefaultValue(initialValue: initialValue)
    }
}

extension UserDefaultsVariable where T: RawRepresentable {

    // this initializer uses specialised T
    convenience init(storageKey : String, initialValue : T) {

        self.init(storageKey: storageKey)
        self.registerDefaultValue(initialValue: initialValue)
    }

    var value: T {
        get {
            let rawValue = UserDefaults.standard.object(forKey: storageKey) as! T.RawValue
            return T(rawValue: rawValue)!
        }
        set {
            UserDefaults.standard.set(newValue.rawValue, forKey: storageKey)
        }
    }

    func registerDefaultValue( initialValue : T ) {
        UserDefaults.standard.register(defaults: [storageKey:initialValue.rawValue])
    }
}