Swift Key Paths 鍵路徑教學

Key Path (鍵路徑) 是一種在 Swift 中功能的強大特性,它允許我們以「引用的方式」來參考一個屬性,而不是直接讀取它的值。

這聽起來很抽象?簡單來說,可以把它想像成屬性的「指標」或「路徑」。

基礎語法

Key Path 的語法是 \Type.Property (反斜線 + 型別 + 點 + 屬性名)。

struct User {
    var name: String
    var age: Int
}

let user = User(name: "Mike", age: 25)

// 傳統存取
print(user.name) // "Mike"

// 使用 Key Path 存取
let path = \User.name // 這只是一個路徑,還沒取值
print(user[keyPath: path]) // "Mike"

我們使用 subscript(keyPath:) 來透過 Key Path 讀取或寫入值。

Key Path 的種類

根據屬性的特性 (可讀、可寫),Key Path 分為幾種型別:

  1. KeyPath<Root, Value>:唯讀 (Read-only)。例如 let 屬性。
  2. WritableKeyPath<Root, Value>:可讀寫 (Read-write)。例如 var 屬性 (對 Value Type)。
  3. ReferenceWritableKeyPath<Root, Value>:參考可讀寫。用於 Class (Reference Type)。
let namePath: WritableKeyPath<User, String> = \User.name

var user = User(name: "Mike", age: 25)
user[keyPath: namePath] = "John" // 修改值
print(user.name) // "John"

常見應用場景

你可能覺得:「那我直接寫 user.name 就好了,幹嘛這麼麻煩?」

Key Path 的威力在於從資料中抽離出邏輯,特別是在高階函式中。

1. 陣列操作 (Map, Filter, Sort)

Swift 的許多高階函式都支援直接傳入 Key Path。

struct Person {
    let name: String
    let age: Int
}

let people = [
    Person(name: "Alice", age: 30),
    Person(name: "Bob", age: 20),
    Person(name: "Charlie", age: 25)
]

// 傳統寫法
let names1 = people.map { $0.name }

// Key Path 寫法 (更簡潔)
let names2 = people.map(\.name) 

// 排序
let sortedPeople = people.sorted(using: KeyPathComparator(\.age))

2. 通用型設定函式

我們可以利用 Key Path 寫出非常通用的 helper function。

// 一個通用的設定器
func configure<T>(_ subject: inout T, path: WritableKeyPath<T, Int>, value: Int) {
    subject[keyPath: path] = value
}

var user = User(name: "Test", age: 0)
configure(&user, path: \.age, value: 18)

3. Identifiable

在 SwiftUI 的 ListForEach 中,我們常需指定 id。這其實就是傳入一個 Key Path。

// \.self 就是一個指向自己的 Key Path
List(items, id: \.self) { item in
    Text(item)
}

// 或是指向屬性
List(users, id: \.id) { user in
    Text(user.name)
}

總結

  • Key Path 是屬性的引用,語法為 \Type.Property
  • 使用 instance[keyPath: path] 來存取值。
  • map, filter, sorted 等高階函式中使用 Key Path 可以讓程式碼更簡潔優雅。