【问题标题】:Swift Dictionary with Protocol Type as Key以协议类型为键的 Swift 字典
【发布时间】:2015-09-03 06:13:44
【问题描述】:

这里有个简短的问题:

我有一个协议protocol SCResourceModel {..} 和一个字典,该字典将使用此协议类型作为键:[SCResourceModel : String]。 这显然不起作用,因为字典中的键必须符合协议Hashable。让我的SCResourceModel 继承自Hashable 或尝试类似[protocol<SCResourceModel, Hashable> : String] 的方法显然效果不佳,因为HashableEquatable 只能用作通用约束,而不能用作类型本身。

我观看了 WWDC 2015,在 Swift 2.0 中可以向协议添加约束,例如:protocol SCResourceModel where Self: Hashable {..},它直接解决了这个问题(非常好)。

无论如何,我的问题是:我可以用当前的 Swift 1.2 版本做类似的事情,并以某种方式使用这个协议作为字典的键吗?或者任何人都可以提出一个很好的解决方法或我可能忽略的其他东西?

我目前看到的 Swift 1.2 的唯一解决方案是将协议转换为继承自例如的类。 NSObject 并且必须被子类化以便在我的 API 中进一步使用。

感谢您的帮助!

【问题讨论】:

  • 你可能是指 Swift 1.2,没有 1.3。它应该像protocol SCResourceModel : Hashable {} 一样工作,或者你有错误吗?如果是这样,请显示您的(最少)代码
  • 我修复了版本 :) 您的建议不起作用,因为这会使 SCResourceModel 成为 Hashable 类型。不允许将 this 作为 Type 仅用作泛型约束(它也不编译)。代码几乎已经在问题中了。一个协议和一个字典,两行。如果您还有其他问题,请告诉我。

标签: ios macos swift dictionary protocols


【解决方案1】:

实际上您不能将协议用作密钥,但您可以使用解决方法。

protocol PropertyStoreKey {
    
    associatedtype Value
    
}

final class PropertyStore {
    
    private var propertyList: [Any] = []
    
    public subscript<K: PropertyStoreKey>(key: K.Type) -> K.Value? {
        get {
            value(of: key)
        }
        set(newValue) {
            update(value: newValue, of: key)
        }
    }
    
    private func value<K: PropertyStoreKey>(of type: K.Type) -> K.Value? {
        guard let index = self.index(of: type) else {
            return nil
        }
        return (propertyList[index] as? PropertyItem<K>)?.value
    }
    
    private func update<K: PropertyStoreKey>(value: K.Value?, of type: K.Type) {
        if let index = self.index(of: type) {
            if let value = value {
                guard var existedItem = propertyList[index] as? PropertyItem<K> else {
                    return
                }
                existedItem.value = value
                propertyList[index] = existedItem
            } else {
                propertyList.remove(at: index)
            }
        } else {
            guard let value = value else {
                return
            }
            let newItem = PropertyItem(type: type, value: value)
            propertyList.append(newItem)
        }
    }
    
    private func index<K: PropertyStoreKey>(of type: K.Type) -> Int? {
        propertyList.firstIndex(where: { $0 is PropertyItem<K> })
    }
    
}

private struct PropertyItem<K: PropertyStoreKey> {
    let type: K.Type
    var value: K.Value
}

用法:

struct TimeKey: PropertyStoreKey {
    typealias Value = Double
}

let propertyStore = PropertyStore()
propertyStore[TimeKey.self] = 5

【讨论】:

    【解决方案2】:

    为什么不使用AnyHashable

      protocol SCResourceModel: Hashable {}
      extension SCResourceModel { // Implement the hashable required methods }
    
      struct SCResource: SCResourceModel {}
    

    现在像这样声明一个字典:

     var container = Dictionary<AnyHashable, String>
     contianer[SCResource] = "My first resource"
    

    【讨论】:

    • 如果协议元类型应该是键,它必须是container[SCResource.self]。但实际上,对于AnyHashable 使用,它必须是container[AnyHashable(SCResource.self)]——但协议类型本身不能是可散列的,所以整个方法都失败了。
    【解决方案3】:

    我可能会考虑以下方向:

    protocol SCResourceModel {
        var hashValue: Int { get }
        func isEqualTo(another: SCResourceModel) -> Bool
    
        // ...
    }
    
    struct SCResourceModelWrapper: Equatable, Hashable {
        let model: SCResourceModel
    
        var hashValue: Int {
            return model.hashValue ^ "\(model.dynamicType)".hashValue
        }
    }
    
    func == (lhs: SCResourceModelWrapper, rhs: SCResourceModelWrapper) -> Bool {
        return lhs.model.isEqualTo(rhs.model)
    }
    
    struct SCResourceModelDictionary<T> {
        private var storage = [SCResourceModelWrapper: T]()
    
        subscript(key: SCResourceModel) -> T? {
            get {
                return storage[SCResourceModelWrapper(model: key)]
            }
            set {
                storage[SCResourceModelWrapper(model: key)] = newValue
            }
        }
    }
    

    【讨论】:

    • 创建自定义词典的好方法。我喜欢!
    【解决方案4】:

    好的,据我所知,没有一种非常好的方法可以将协议本身作为密钥。但我很确定协议名称的字符串版本可以很好地满足您的目的。作为奖励,您还可以将协议对象作为字典中的值(如果这对您的情况有用)

    问题是,我也找不到在 Swift 中执行此操作的好方法,但这是我在 Objective-C 中提出的,也许您会比我更好地找到执行此操作的方法在斯威夫特:

    // With a protocol declared...
    @protocol TestProtocol <NSObject>
    @end
    
    @implementation
    
    // Inside the implementation you can use NSStringFromProtocol()
    Protocol *proto = @protocol(TestProtocol);
    NSLog(@"Protocol: %@", NSStringFromProtocol(proto));
    
    @end
    

    输出:

    Protocol: TestProtocol
    

    该代码的@protocol 部分是我不确定如何在 Swift 中执行的部分,如果情况变得更糟,您可以随时连接到 Objective-C 文件。希望这能给你一些想法!

    【讨论】:

    • 感谢您的想法
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 2018-05-23
    相关资源
    最近更新 更多