> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/features/key-path.md).

# Key Path

{% tabs %}
{% tab title="重點" %}
{% hint style="info" %}
**Keypaths** allow you to **refer** to **properties** <mark style="color:red;">**without invoking**</mark> them – you hold a **reference** to the property itself, rather than reading its value.
{% endhint %}

{% hint style="info" %}
Key paths 主要三種變體(還有其他)

* <mark style="color:red;">**KeyPath**</mark>:  **read-only** access to a property.
* <mark style="color:red;">**WritableKeyPath**</mark>:  **readwrite** access to a **mutable** property with **value semantics** (so the instance in question also needs to be mutable for writes to be allowed).
* <mark style="color:red;">**ReferenceWritableKeyPath**</mark>: can only be used with **reference types**, and provides **readwrite** access to any **mutable** property.
  {% endhint %}

{% hint style="info" %}
**Keypaths** in Swift have a few more types, which mostly revolve around <mark style="color:red;">**type-erasure**</mark>, like with [Any](https://learnappmaking.com/swift-any-anyobject-how-to/). When you combine or allow <mark style="color:red;">**multiple keypaths**</mark>, for example in an <mark style="color:red;">**array**</mark>, you can use the [**PartialKeyPath**](https://developer.apple.com/documentation/swift/partialkeypath) and [**AnyKeyPath**](https://developer.apple.com/documentation/swift/anykeypath) to construct types that fit multiple keypaths.
{% endhint %}
{% endtab %}

{% tab title="📗 參考" %}

* [x] [How Swift keypaths let us write more natural code](https://www.hackingwithswift.com/articles/57/how-swift-keypaths-let-us-write-more-natural-code) - Hacking with Swift
* [x] Sundell - [The power of key paths in Swift](https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)
* [x] LearnAppMaking - [Keypaths in Swift Explained](https://learnappmaking.com/swift-keypath-how-to/)
* [x] [KeyPath 在 Swift 中的妙用](https://codertw.com/程式語言/728293/) - 程式前沿
* [x] DEV - [Keypaths in Swift](https://dev.to/ahmed_komsan12/keypaths-in-swift-18o6)
* [x] AppVenture.me ⟩ [Introduction to Swift Keypaths](https://appventure.me/guides/keypaths/intro.html)  ⭐️
  {% endtab %}

{% tab title="📘 手冊" %}

* Swift Evolution ⟩&#x20;
  * [SE0161](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md): Smart KeyPaths: Better Key-Value Coding for Swift
  * [SE0249](https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md): Key Path Expressions as Functions
* Swift  ⟩ Standard Library  ⟩&#x20;
  * [Key-Path Expressions](https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions)
    * [PartialKeyPath](https://developer.apple.com/documentation/swift/partialkeypath)
    * [AnyKeyPath](https://developer.apple.com/documentation/swift/anykeypath) - <mark style="color:red;">**type-erased**</mark> key path, from any root type to any resulting value type.
    * [KeyPath](https://developer.apple.com/documentation/swift/keypath)
    * [WritableKeyPath](https://developer.apple.com/documentation/swift/writablekeypath)
    * [ReferenceWritableKeyPath](https://developer.apple.com/documentation/swift/referencewritablekeypath)&#x20;
      {% endtab %}

{% tab title="定義" %}

```swift
// declarations: inheritance chain
class AnyKeyPath
class PartialKeyPath<Root> : AnyKeyPath
class KeyPath<Root, Value> : PartialKeyPath<Root>
class WritableKeyPath<Root, Value> : KeyPath<Root, Value>
class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value>

```

{% endtab %}

{% tab title="👥  相關" %}

* [Key Path Expressions as Functions](/ios/features/key-path/key-path-as-functions.md)
* [Sorting Swift collections](https://www.swiftbysundell.com/articles/sorting-swift-collections) - Sundell ⭐️&#x20;
* [sort using key paths](/ios/algorithms/sort/sort-using-key-paths.md)
* [seq.sorted(\_:)](/ios/swift/collections/sequence/seq.sorted-_.md)
  {% endtab %}

{% tab title="⬇️ 應用" %}

* [operator (\~=)](/ios/swift/pattern-matching/operator.md) - pattern matching using keypath
  {% endtab %}
  {% endtabs %}

## Examples

{% tabs %}
{% tab title="id key" %}
:point\_right: [replit](https://replit.com/@pegasusroe/Swift-Keypath#main.swift)

```swift
// ⭐ 下面兩種型別雖然都有「可以當作 ID」的屬性，但屬性名稱不一樣。

struct Person {
    // social security number
    var ssn: String         // ⭐ id key for `Person`
    var name: String
}

struct Book {
    var isbn: String        // ⭐ id key for `Book`
    var title: String
}

/* -------- HasID protocol -------- */

// ⭐ 用 `HasID` 協定來統一規範這種有「ID 屬性」的類別。
// ⭐ 這種做法的好處是：「ID 屬性」不需要真的叫 `id`，叫其他名稱也可以。
protocol HasID {
    // ⭐ 2. 用 `IDType` 來稱呼此「ID 屬性」的類別
    associatedtype IDType
    // ⭐ 1. 用 keypath 來指定哪個屬性是「ID 屬性」
    static var idKey: WritableKeyPath<Self, IDType> { get }
}

/* -------- protocol extension (default behaviors) -------- */

extension HasID {
    func printID(){
        print(self[keyPath: Self.idKey])
    }
}

/* -------- protocol conformances -------- */

extension Person: HasID {
    static let idKey = \Person.ssn  // ⭐ WritableKeyPath<Person, String>
}

extension Book: HasID {
    static let idKey = \Book.isbn   // ⭐ WritableKeyPath<Book, String>
}

/* -------- main -------- */

let taylor = Person(ssn: "555-55-5555", name: "Taylor Swift")
let book = Book(isbn: "1234-5678", title: "Snow White")

taylor.printID()    // 555-55-5555
book.printID()      // 1234-5678

print(type(of: Person.idKey))       // WritableKeyPath<Person, String>
print(type(of: Book.idKey))         // WritableKeyPath<Book, String>

```

{% endtab %}

{% tab title="append" %}

```swift
// User
struct User {
    var fullName: String
    var email: String?
    var age: Int
}

let user = User(fullName: "Mike", age: 16)

let fullNamePath = \User.fullName      // ⭐️ KeyPath<User, String>
user[keyPath: fullNamePath]            // get: user.fullName
user[keyPath: fullNamePath] = "John"   // set: user.fullName

// appending key paths: ╭─ key path ─╮ ╭─────╮
let fullNameEmptyPath = \User.fullName.isEmpty  // ⭐️ KeyPath<User, Bool>
user[keyPath: fullNameEmptyPath]       // user.fullName.isEmpty

let isEmptyPath = \String.isEmpty

// ⭐️ appending key path
let fullNameIsEmpty = fullNamePath.appending(path: isEmptyPath)
user[keyPath: fullNameIsEmpty] // user.fullName.isEmpty
```

{% endtab %}

{% tab title="type-erase/cast" %}

```swift
var user = User(username: "Hello")

// ⭐️ type-erase to `AnyKeyPath`
let keyPath: AnyKeyPath = \User.username   // ⭐️ read-only

/* -------- ⭐️ type-cast -------- */

// ⭐️ if let ... as? ...
if let writableUsername = keyPath as? WritableKeyPath<User, String> {
   user[keyPath: writableUsername] = "World"    // ⭐️ read-write
}

// ⭐️ case let ... as ...
switch keyPath {
    case let a as KeyPath<User, String>: print(user[keyPath: a])
    case let a as KeyPath<User, Int>   : print(user[keyPath: a])
    default: print("Unknown keypath type")
}
```

{% endtab %}
{% endtabs %}
