Swift: 在参数/成员变量中设置协议的关联类型

Swift: Set protocol's associated type in argument / member variable

我正在尝试创建一个通用协议,它可以在我的应用程序的各个部分重复使用。以前,我已将 KeyValueStore 硬编码为仅将 StringInt 分别用于 KeyValue。我 运行 遇到一个问题,我想要一个不同的消费者,class ConsumerB,使用 KeyValueStoreKey/ValueInt/Int分别.

protocol KeyValueStore {
    associatedtype Key
    associatedtype Value
    func get(key: Key) -> Value
    func set(key: Key, value: Value)
}

对于最初的实现,我有两个 class 符合协议。 UserDefaultsStore用于在运行时存储数据,MemoryKeyValueStore用于单元测试。

class UserDefaultsStore: KeyValueStore {
    func get(key: String) -> Int { return 0
        // Implementation
    }
    
    func set(key: String, value: Int) {
        // Implementation
    }
}

class MemoryKeyValueStore: KeyValueStore {
    var values: [String: Int] = [:]
    func get(key: String) -> Int { return values[key]! }
    func set(key: String, value: Int) { values[key] = value}
}

因为他们都使用 KeyValueStore 硬编码类型,所以我可以在运行时提供存储实现。

class ConsumerA {
    var keyValueStore: KeyValueStore
    
    init(store: KeyValueStore) {
        keyValueStore = store
    }
  
    func example() {
        keyValueStore.set(key: "Hello", value: 5)
    }
}

添加关联类型后,出现以下错误。这是有道理的,在编译 ConsumerA 时,无法知道 KeyValueKeyValueStore 参数和成员变量中采用什么类型。然而,在四处寻找之后,我似乎无法找到关于 如何正确声明 KeyValue 应该在 ConsumerA [=64= 中的类型的明确答案].

example函数中,明明会使用StringInt,但是如何设置argument/member变量只允许[=64] =]es 符合 KeyValueStore 并且 Key/Value 设置为 String/Int?

Protocol 'KeyValueStore' can only be used as a generic constraint because it has Self or associated type requirements

编辑: 根据晚上晚些时候发现的 this 媒体文章,我最终选择了不同的路线。 虽然看起来 Swift 本身应该提供一种方法来将具有关联类型的协议声明为 argument/variable 类型,但这里是解决方法。

class AnyKeyValueStore<Key, Value>: KeyValueStore {
    private let _set: (Value?, Key) -> Void
    private let _object: (Key) -> Value?
    
    init<U: KeyValueStore>(_ keyValueStore: U) where U.Key == Key, U.Value == Value {
        _set = keyValueStore.set
        _object = keyValueStore.object
    }
    
    func set(value: Value?, forKey key: Key) {
        _set(value, key)
    }
    
    func object(forKey key: Key) -> Value? {
        return _object(key)
    }
}

ConsumerA 需要通用。将 ModuleName 替换为您的实际模块名称。

class ConsumerA<KeyValueStore: ModuleName.KeyValueStore>
where KeyValueStore.Key == String, KeyValueStore.Value == Int {

此外,您的方法对应该是下标。

subscript(key: Key) -> Value { get set }
keyValueStore["Hello"] = 5