如何在初始化程序中为受约束的泛型获取动态调度
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])
}
}
下面的代码工作得很好:
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])
}
}