简体   繁体   中英

Swift 5 storing and passing KeyPaths

Let's say I have the following class:

class User: NSObject {
  var name = "Fred"
  var age = 24
  var email = "fred@freddy.com"
  var married = false
}

I want to be able to write a generic function that takes in a list of KeyPath s for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath 's Value is not known, and will be different for each time. What do I have to do to make this work generically?

Consider the following:

struct KeyPathProperties<T> {
  var name: String
  var relatedKeyPaths: [KeyPath<T, Any>]
}

extension KeyPath where Root == User {
  var properties: KeyPathProperties<Root> {
    switch self {
      case \Root.name:
        return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
      default:
        fatalError("Unknown key path")
    }
  }
}

This line fails to compile:

return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])

with this error:

Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'

This is what I wish to be able to do, for instance:

let myUser = User()

var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])

for keyPath in props.relatedKeyPaths {
  print("Value: \(myUser[keyPath: keyPath])")
}

The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User . I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.

This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.

MORE INFORMATION:

At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:

for modifiedKeyPath in self.modifiedProps { 
  print ("\(self[keyPath: modifiedKeyPath])" 
}

In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties . How do I achieve this?

SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding @objc to the class properties). I can store an array of keyPaths as strings and later do:

let someKeyPath = #keyPath(User.email)
...

myUser.value(forKeyPath: someKeyPath)

I just cannot do this with Swift 4 KeyPaths generically.

The error tells you what your misconception is:

Cannot convert value of type 'KeyPath<User, Int>' 
    to expected element type 'KeyPath<User, Any>'

You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.

If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.

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