Keypaths
Tags: swift, keypath, advanced
Published: 2020-08-15 23:54:35

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.

```swift
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
  }
}
```

```swift
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))
  }
}
```

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**.

```swift
let jill = Jill(name: "Jill Valentine")
jill.isInfected // <= false
```

We can just get the same result using `keypaths`

```swift
// 1
let isInfected = \Jill.isInfected

// 2
let isJillInfected = jill[keypath: isInfected]
```

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.

```swift
let characterName = \Jill.basicInfo.name
let jillName = jill[keyPath: characterName]
```

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.

```swift
let upSlot = \Jill.basicInfo.quickSlots.up
let jillUpSlot = jill[keyPath: upSlot]
```

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.

```swift
// 1
let basicInfoPath = \Jill.basicInfo

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

// 3
let jillRightSlot = jill[keyPath: rightSlotPath]
```

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!.

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

// 2
jill[keyPath: leftSlotPath] = "M3 Shotgun"
```

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.

{% link https://dev.to/fdorado985/subscripts-dynamic-member-lookup-2dgk %}

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

```swift
@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]
  }
}
```

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.

```swift
let carlos = Carlos(name: "Carlos Oliveira")
carlos.name
```

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 👨‍💻!