DEV Community

Juan Dorado
Juan Dorado

Posted on

Keypaths

Have you ever listen the keypath keyword? - No

If you have used SwiftUI you may be familiar with List using identifiers just like this... List(data, id: \.id) { ... } on this code \.id is the keypath.

In short words keypaths are store references to your properties

What amazing thing huh?! 😁

Much easier to explain this using our handy dandy?... (📣 notebook!) - sorry, no!, example! (Bad joke from Blue's clues in case you didn't get it).

Let's use this simple classes to work with.

class BasicInfo {

  let name: String
  var quickSlots: (up: String?, left: String?, right: String?, down: String?)

  init(name: String, quickSlots: (up: String?, left: String?, right: String?, down: String?)) {
    self.name = name
    self.quickSlots = quickSlots
  }
}
Enter fullscreen mode Exit fullscreen mode
class Jill {

  var basicInfo: BasicInfo
  var isInfected = false

  init(name: String) {
    self.basicInfo = BasicInfo(name: name, quickSlots: (up: "G19 Handgun", left: nil, right: "Survival Knife", down: nil))
  }
}
Enter fullscreen mode Exit fullscreen mode

So in here we just have a simple class that have some basic info, later a Jill(Resident Evil character) class that depends from BasicInfo.

We are not going to dive deep on this class because it is not what we are talking about.

To get access to properties we can use dot-notation.

let jill = Jill(name: "Jill Valentine")
jill.isInfected // <= false
Enter fullscreen mode Exit fullscreen mode

We can just get the same result using keypaths

// 1
let isInfected = \Jill.isInfected

// 2
let isJillInfected = jill[keypath: isInfected]
Enter fullscreen mode Exit fullscreen mode
  1. We just need a backslash then the path to reference, our class and the property we want to reference, look that I am using the class itself and not the instance.

  2. Using keypath's subscript we can get the value of the property that we are referencing to.

But... our property doesn't need to be directly on our class, it can be from several levels deep

Take a look at this.

let characterName = \Jill.basicInfo.name
let jillName = jill[keyPath: characterName]
Enter fullscreen mode Exit fullscreen mode

As you can see we are now trying to access the name from the basicInfo property, and the access, it is just the same way.

Additional to deep levels, guess what? - We can use it with tuples.

A tuple is just a set of variables composed in one.

let upSlot = \Jill.basicInfo.quickSlots.up
let jillUpSlot = jill[keyPath: upSlot]
Enter fullscreen mode Exit fullscreen mode

And there you go, the same result we were expecting!

Also, we don't need to create the full path at first, we can have a base keypath and just append another to it.

// 1
let basicInfoPath = \Jill.basicInfo

// 2
let rightSlotPath = basicInfoPath.appending(path: \.quickSlots.right)

// 3
let jillRightSlot = jill[keyPath: rightSlotPath]
Enter fullscreen mode Exit fullscreen mode
  1. We have our base keypath that references the basicInfo property
  2. We ju.st append the new route using the root (\) and moving deeper, in this case our right property inside our tuple.
  3. We use our keypath such as before.

Until know we have been using this feature to read values, but... we can set values to it!.

// 1
let leftSlotPath = \Jill.basicInfo.quickSlots.left

// 2
jill[keyPath: leftSlotPath] = "M3 Shotgun"
Enter fullscreen mode Exit fullscreen mode
  1. We create the reference to our property, such as before.
  2. Let's suppose we got a new gun and it automatically is assigned to our left side, and this is as simple as just assign it.

There is one more feature we can take approach of... Dynamic Member Lookup.

I have a post related to this topic using subscripts, it is a 3 minutes approximately lecture.

In short words, using Dynamic Member Lookup we can make Swift a dynamic language related to Phyton or PHP.

@dynamicMemberLookup
class Carlos {

  var basicInfo: BasicInfo

  init(name: String) {
    self.basicInfo = BasicInfo(name: name, quickSlots: (up: "Assault Rifle", left: "G18 Handgun", right: "Survival Knife", down: nil))
  }

  subscript(dynamicMember keyPath: KeyPath<BasicInfo, String>) -> String {
    return basicInfo[keyPath: keyPath]
  }
}
Enter fullscreen mode Exit fullscreen mode

I recommend you to take a look to the previous post about Dynamic Member Lookup, where I explain a little bit more how this works.

let carlos = Carlos(name: "Carlos Oliveira")
carlos.name
Enter fullscreen mode Exit fullscreen mode

Finally we just need to access to the dynamic property we want to, we doesn't need to create our keypath.

And with this you got some more knowledge and learn a new skill to add it to your list!.

Happy coding 👨‍💻!

Top comments (0)