KeyPath in Swift: Usage.
Tags: swift, mobile, ios, programming
Published: 2023-01-29 07:57:55

A key path in Swift refers to a property or subscript of a type, so its only usage is to read/write to that property/subscript using the key path.

## Access a value
To access a value using a key path, pass a key path to the `subscript(keyPath:)` subscript, which is **available on all types**. You can use it to read or write based on the type of a key path and an instance.

Her is an example of using key path to read/write to `user.role`.

```swift
var user = User(
    name: "Sergey",
    email: "sergey.leschev@gmail.com",
    address: nil,
    role: .admin)

let userRoleKeyPath = \User.role
//  WritableKeyPath<User, Role>

// 1
let role = user[keyPath: userRoleKeyPath]

print(role) // admin

// 2
user[keyPath: userRoleKeyPath] = .guest

print(user.role) // guest
```

1 Use keypath to read the role value.
2 Use keypath to set the role value.

One thing to note here is that even with `WritableKeyPath`, your struct still needs to be `var` to be able to write. Try to set a new value on a `let` value would cause a compile error.

**WritableKeyPath** can use on both **let** and **var** for reference types.

## Caveats

Constructing a key path using unsafe expressions can cause the same runtime error as using them on an instance.

Here is an example using forced unwrapping expressions (`!`) and array `subscript(index: Int)` in key paths.

```swift
let fourthIndexInteger = \[Int][3]
let integers = [0, 1, 2]
print(integers[keyPath: fourthIndexInteger])
// Fatal error: Index out of range

let user = User(
    name: "Sergey",
    email: "sergey.leschev@gmail.com",
    address: nil,
    role: .admin)

let forceStreetAddress = \User.address!.street
print(user[keyPath: forceStreetAddress])
// Fatal error: Unexpectedly found nil while unwrapping an Optional value
```

## Identity Key Path

We also have a special path that can refer to a whole instance instead of a property. We can create one with the following syntax, `\.self`.

The result of the identity key path is the `WritableKeyPath` of the whole instance, so you can use it to access and change all of the data stored in a variable in a single step.

```swift
var foo = "Foo"
// 1
let stringIdentity = \String.self

//  WritableKeyPath<String, String>

foo[keyPath: stringIdentity] = "Bar"
print(foo) // Bar

struct User {
  let name: String
}
var user = User(name: "John")
// 2
let userIdentity = \User.self

// WritableKeyPath<User, User>

user[keyPath: userIdentity] = User(name: "Doe")
print(user) // User(name: "Doe")
```

1 Identity key path to String.
2 Identity key path to User.

## Use Cases

Key paths seem like another way of reading and writing value out of an instance. But the fact that we can treat an **ability to read/write a value in the form of a variable** makes the use cases broader than read and write.

It is okay if you can't think of any use cases of key paths. As I mentioned initially, it is a kind of metaprogramming that is needed for some specific scenario.

It is quite hard to tell you exactly where you should use the key paths. I think it is easier to show you where they are used. If you have seen enough use cases, I think you will eventually know where you can use them (or don't).

Here are some places where key paths are used in real API.

## Key paths as protocols alternative
In SwiftUI, we can create views from a collection of `Identifiable` data. The only requirement of the `Identifiable` protocol is a `Hashable` variable named `ID`.

```swift
struct User: Identifiable {
    let name: String
    
    // 1
    var id: String {

        return name
    }
}

let users: [User] = [
    User(name: "John"),
    User(name: "Alice"),
    User(name: "Bob"),
]

struct SwiftUIView: View {
    var body: some View {
        ScrollView {
            ForEach(users) { user in
                Text(user.name)
            }
        }
    }
}
```

1 Use name to uniquely identify user. This is for demonstration only, you should use something more unique for an ID, or bad things will happen with your list.

`Identifiable` is a protocol to uniquely identify an item in a list. SwiftUI also provides an alternative initializer using a key path.

## KeyPath
Instead of forcing data type to conform `Identifiable` protocol, this alternative initializer let data type specified a path to its underlying data identity.

```swift
// 1
struct User {

    let name: String
}

struct SwiftUIView: View {
    var body: some View {
        ScrollView {
            // 2

            ForEach(users, id: \.name) { user in
                Text(user.name)
            }
        }
    }
}
```

1 User no longer conform to `Identifiable` protocol.
2 We specify path to property that can uniquly identify `User` struct.

Instead of using a protocol to define a common interface for getting some value, we can use a key path to inject that value instead. **Keypath provided a way to transfer read access to other functions.**

The interesting point here is the ability to reference to read/write access resulting in the equivalent functionality as **Identifiable** protocol. The scope of key paths can be broader than just read/write.

## Key paths as functions

We can also look at a key path in the form of function.

The key path expression `\Root.value` can represent as a function with the following signature `(Root) -> Value`.

In this example, we try to map user names out of an array of users.

`map(_:)` has the following signature. It accepts a transform parameter closure with array element (`Element`) as argument and return type that you want to transform to (`T`).

```swift
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
```

Let try it without a key path.

```swift
struct User {
    let name: String
}

let users = [
    User(name: "Sergey"),
    User(name: "Alice"),
    User(name: "Bob")
]

let userNames = users.map { user in
    return user.name
}
// ["Sergey", "Alice", "Bob"]
```

In this example, `map(_:)` accept a parameter of function `(Element) -> Value`. Based on our claim, we should be able to use a key path expression `\Element.Value` instead. Let's try to create a new override of a map that takes a key path instead.

```swift
extension Array {
    func map<Value>(_ keyPath: KeyPath<Element, Value>) -> [Value] {        
        return map { $0[keyPath: keyPath] }
    }
}

let userNames = users.map(\.name)
// ["Sergey", "Alice", "Bob"]
```

As you can see, we can create an equivalent implementation for a function that expected `(Root) -> Value` with a key path of `\Root.Value`. In Swift 5.2, we don't even have to do the conversion ourselves. This functionality is built right into the Swift under this [proposal](https://github.com/sergeyleschev/swift-evolution/blob/main/proposals/0249-key-path-literal-function-expressions.md).

**As a result, a key path expression `\Root.value` can use wherever functions of `(Root) -> Value` are allowed.**

---

Previous Articles: 
- [Types of KeyPath in Swift](https://dev.to/sergeyleschev/types-of-keypath-in-swift-2o6m)
- [KeyPath in Swift: Syntax](https://dev.to/sergeyleschev/keypath-in-swift-1mpg)

---

**Contacts**
I have a clear focus on time-to-market and don't prioritize technical debt. And I took part in the Pre-Sale/RFX activity as a System Architect, assessment efforts for Mobile (iOS-Swift, Android-Kotlin), Frontend (React-TypeScript) and Backend (NodeJS-.NET-PHP-Kafka-SQL-NoSQL). And I also formed the work of Pre-Sale as a CTO from Opportunity to Proposal via knowledge transfer to Successful Delivery.

🛩️ #startups #management #cto #swift #typescript #database
📧 Email: sergey.leschev@gmail.com
👋 LinkedIn: https://linkedin.com/in/sergeyleschev/
👋 LeetCode: https://leetcode.com/sergeyleschev/
👋 Twitter: https://twitter.com/sergeyleschev
👋 Github: https://github.com/sergeyleschev
🌎 Website: https://sergeyleschev.github.io
🌎 Reddit: https://reddit.com/user/sergeyleschev
🌎 Quora: https://quora.com/sergey-leschev
🌎 Medium: https://medium.com/@sergeyleschev
🖨️ PDF Design Patterns: [Download](https://sergeyleschev.github.io/sergeyleschev-design-patterns.pdf)