简体   繁体   中英

Storing KeyPaths in Dictionary/HashMap

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM