Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift KeyPath on optional value

If I have

class Info {
    var name: String?
}

class User {
    var info: Info?
}

class WrappedClass {
    var user: User = User()
}


let nameKeyPath = \WrappedClass.user.info?.name //Gets me KeyPath
let referencedNameKeyPath = \WrappedClass.user.info!.name //Gets me ReferenceWritableKeyPath

nameKeyPath gets me a KeyPath which I can't later on use to modify the name value, but if I force unwrap it I get a ReferenceWritableKeyPath which is what I'm after.

Unfortunately using referencedNameKeyPath with a nil value along the line expectedly crashes, due to unexpectedly finding nil.

My question is is there a way to convert the KeyPath to a ReferenceWritableKeyPath or somehow unwrap it along the way?

like image 314
Andrew Avatar asked Mar 06 '23 22:03

Andrew


2 Answers

You can also use optional chaining on keypaths. Create two keypaths one for user and the other one for name in info.

let infoKeyPath = \WrappedClass.user.info

let nameKeyPath = \Info.name

Now, use optional chaining with keypath and it will yield String? as the result.

let name = wrappedInstance[keyPath: infoKeyPath]?[keyPath: nameKeyPath]
like image 107
Sandeep Avatar answered Mar 09 '23 12:03

Sandeep


Try having the key path as a computed optional variable. Like that:

class WrappedClass {
  var user: User = User()

  var nameKeyPath: ReferenceWritableKeyPath<WrappedClass, String?>? {
    guard let _ = user.info else { return nil }
    return \WrappedClass.user.info!.name
  }
}

You still end up with the force unwrap notation but it should not cause any issues since you specifically guard for it.

That way you can use the computed key path in a safe and convenient way:

let instance = WrappedClass()

if let nameKeyPath = instance.nameKeyPath {
  instance[keyPath: nameKeyPath] = "Nikola"
}
like image 30
Nikola Kirev Avatar answered Mar 09 '23 11:03

Nikola Kirev