I'm facing a problem when hashing a ReferenceWritableKeyPath
. It appears that the hash function also takes the generic properties of the ReferenceWritableKeyPath
into account when hashing the key path. I've included sample code to show why this is a problem:
struct TestStruct<T> {
// This function should only be callable if the value type of the path reference == T
func doSomething<Root>(object: Root, path: ReferenceWritableKeyPath<Root, T>) -> Int {
// Do something
print("Non-optional path: \(path) \(path.hashValue)")
return path.hashValue
}
}
let label = UILabel()
let textColorPath = \UILabel.textColor
let testStruct = TestStruct<UIColor>()
let hash1 = testStruct.doSomething(object: label, path: \.textColor)
let hash2 = textColorPath.hashValue
print("Optional path: \(textColorPath) \(hash2)")
If you run the code above, you will notice that hash1 and hash2 are different despite being paths to the same property of the UILabel.
This happens because the 1st ReferenceWritableKeyPath
has a Value
that is UIColor
while the 2nd ReferenceWritableKeyPath
has a Value
that is Optional<UIColor>
My project requires the ReferenceWritableKeyPath
s to be stored in a dictionary so that there is only one keyPath for each property of the associated object (UILabel). Since the hashes are different, this means that the same path will be stored as 2 different keys in the dictionary.
Does anyone know of a way that I can get this to work?
~Thanks in advance
Make textColorPath
also be non-optional, to match:
let textColorPath = \UILabel.textColor!
or be explicit about the type:
let textColorPath: ReferenceWritableKeyPath<UILabel, UIColor> = \.textColor
The underlying problem is that \.textColor
is an implicitly unwrapped optional rather than a "real" optional. In some contexts that gets treated as the underlying type, and in others it's promoted to an Optional. The reason it's an implicitly unwrapped optional is because it is legal to set textColor
to nil. But the value you read will never be nil.
As @Rob Napier pointed out, the problem was with the generic types themselves. The way I fixed the problem was by splitting the doSomething
into two separate methods:
func doSomething<Root>(object: Root, path: ReferenceWritableKeyPath<Root, T?>) -> Int {
// Do something
print("Non-optional path: \(path) \(path.hashValue)")
return path.hashValue
}
func doSomething<Root>(object: Root, path: ReferenceWritableKeyPath<Root, T>) -> Int {
// Do something
print("Non-optional path: \(path) \(path.hashValue)")
return path.hashValue
}
The 1st one will get called when T
is an optional type such as in the example above (where UIColor
can be nil). The 2nd one gets called when the keyPath points to a non-optional property. Swift is pretty smart so I guess it's able to figure out which metthod to call despite them having almost duplicate headers.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.