Reputation: 1725
short question here:
I got a protocol protocol SCResourceModel {..}
and a dictionary which shall use this protocol type as key: [SCResourceModel : String]
.
This obviously does not work, as a key in a dictionary has to conform to the protocol Hashable
. Making my SCResourceModel
inherit from Hashable
or trying something like this [protocol<SCResourceModel, Hashable> : String]
obviously does not work as well, since Hashable
or Equatable
can only be used as generic constraints and not as types themselves.
I watched the WWDC 2015 and in Swift 2.0 it is possible to add constraints to a protocol like: protocol SCResourceModel where Self: Hashable {..}
which directly addresses this issue (very nice).
Anyway, my question is: Can I do something similar with the current Swift 1.2 version and somehow use this protocol as the key of a dictionary? Or can anyone propose a nice workaround or something else that I might have overlooked?
The only solution I see at the moment for Swift 1.2, is to transform the protocol to a class which inherits from e.g. NSObject and must be subclassed for further usage in my API.
Thanks for your help!
Upvotes: 6
Views: 7430
Reputation: 818
Actually you can't use a protocol as a key, but you can use a workaround.
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
}
Usage:
struct TimeKey: PropertyStoreKey {
typealias Value = Double
}
let propertyStore = PropertyStore()
propertyStore[TimeKey.self] = 5
Upvotes: 1
Reputation: 2100
Why not use AnyHashable
?
protocol SCResourceModel: Hashable {}
extension SCResourceModel { // Implement the hashable required methods }
struct SCResource: SCResourceModel {}
Now declare a Dictionary like this:
var container = Dictionary<AnyHashable, String>
contianer[SCResource] = "My first resource"
Upvotes: 0
Reputation: 10136
I would probably think in the direction of:
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
}
}
}
Upvotes: 6
Reputation: 73
Alright, so as far as I can tell, there isn't a really nice way to have the protocol itself as a key. But I'm pretty sure a string version of the protocol name would work nicely for your purpose. As a bonus, you can also have protocol objects as the value in a dictionary (if that is useful in your circumstance)
Thing is, I can't find a nice way to do this in Swift either, but here is what I've come up with in Objective-C, maybe you would be better than me at finding a way to do this in Swift:
// 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
outputs:
Protocol: TestProtocol
The @protocol
part of that code is the part I'm not real sure how to do in Swift and if worse comes to worst, you can always bridge to an Objective-C file. Hope this gives you some ideas!
Upvotes: 2