KeyPath là một đường dẫn kiểu đến thuộc tính của một đối tượng trong Swift, được biểu diễn như một giá trị hạng nhất. Không giống như khóa chuỗi trong KVC (Key-Value Coding), KeyPath được trình biên dịch kiểm tra tại thời điểm biên dịch: trình biên dịch biết kiểu của đối tượng gốc, kiểu của thuộc tính và có thể đảm bảo rằng đường dẫn tồn tại. KeyPath được sử dụng trong Combine để liên kết phản ứng, trong SwiftUI để liên kết hai chiều và trong thư viện chuẩn để sắp xếp và lọc bộ sưu tập. Theo Apple Developer, 2025, KeyPath là khối xây dựng cơ bản cho lập trình hàm trong Swift.
Các điểm chính
KeyPath là một kiểu từ thư viện chuẩn Swift đại diện cho một đường dẫn đến thuộc tính của một đối tượng. Không giống như truy cập thủ công vào thuộc tính bằng ký hiệu dấu chấm (object.property) hoặc khóa chuỗi (value(forKey:)), KeyPath là một đối tượng hạng nhất: nó có thể được truyền như đối số, lưu trữ trong biến và kết hợp.
KeyPath được viết bằng dấu gạch chéo ngược và đường dẫn đến thuộc tính: \Person.name. Đường dẫn có thể lồng nhau: \Person.address.city. Trình biên dịch xác minh rằng Person có thuộc tính name và name thực sự là String. Nếu thuộc tính không tồn tại hoặc kiểu sai, mã sẽ không biên dịch.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (chỉ đọc)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
Truy cập thuộc tính qua KeyPath được thực hiện thông qua chỉ mục con object[keyPath: path]. Đối với thuộc tính chỉ đọc, KeyPath được sử dụng. Nếu thuộc tính có thể thay đổi (var), WritableKeyPath có sẵn. Đối với kiểu tham chiếu, ReferenceWritableKeyPath cho phép sửa đổi thuộc tính trực tiếp thông qua KeyPath.
KeyPath có một hệ thống phân cấp kiểu con phản ánh mức độ truy cập vào thuộc tính. Hệ thống phân cấp cơ bản là: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Mỗi kiểu con thêm khả năng.
KeyPath — chỉ đọc. WritableKeyPath — đọc và ghi cho kiểu giá trị (struct). ReferenceWritableKeyPath — đọc và ghi cho kiểu tham chiếu (class). Swift tự động chọn kiểu con phù hợp dựa trên ngữ cảnh.
| Kiểu | Truy cập | Kiểu đối tượng | Thay đổi |
|---|---|---|---|
| KeyPath | Chỉ đọc | Bất kỳ | Không |
| WritableKeyPath | Đọc và ghi | Kiểu giá trị (inout) | Qua ngữ cảnh mutating |
| ReferenceWritableKeyPath | Đọc và ghi | Kiểu tham chiếu | Gán trực tiếp |
// WritableKeyPath cho kiểu giá trị
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath cho lớp
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Một điểm khác biệt quan trọng: đối với kiểu giá trị, thiết lập qua KeyPath yêu cầu var (ngữ cảnh inout), trong khi đối với kiểu tham chiếu chỉ cần một thuộc tính có thể thay đổi. Điều này phù hợp với ngữ nghĩa chung của Swift: kiểu giá trị được truyền theo giá trị, vì vậy thay đổi yêu cầu tính khả biến của container.
KeyPath đóng vai trò trung tâm trong Combine và SwiftUI. Trong Combine, toán tử assign sử dụng KeyPath để liên kết giá trị của nhà xuất bản với thuộc tính của đối tượng. Trong SwiftUI, Binding sử dụng KeyPath để liên kết hai chiều giữa mô hình và chế độ xem.
Toán tử assign(to:on:) chấp nhận một ReferenceWritableKeyPath và một đối tượng. Khi nhà xuất bản phát ra giá trị, nó tự động được ghi vào thuộc tính được chỉ định. Đây là cách khai báo để quản lý trạng thái mà không cần gán thủ công.
import Combine
class SettingsViewModel: ObservableObject {
@Published var volume: Float = 0.5
var cancellables = Set<AnyCancellable>()
func bindSlider(publisher: AnyPublisher<Float, Never>) {
publisher
.assign(to: \SettingsViewModel.volume, on: self)
.store(in: &cancellables)
}
}
Trong SwiftUI, @Binding sử dụng KeyPath để kết nối chế độ xem cha và con. Cha truyền một Binding
KeyPath cho phép viết các hàm sắp xếp và lọc linh hoạt và an toàn về kiểu. Thay vì truyền closure mỗi lần, bạn có thể truyền KeyPath đến thuộc tính mà bạn muốn sắp xếp bộ sưu tập. Điều này làm cho mã sạch hơn và giảm sự trùng lặp.
Hàm sorted
extension Sequence {
func sorted<Value: Comparable>(
by keyPath: KeyPath<Element, Value>,
ascending: Bool = true
) -> [Element] {
ascending
? self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
: self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
}
}
let sortedByName = people.sorted(by: \Person.name)
let sortedByAgeDesc = people.sorted(by: \Person.age, ascending: false)
Việc triển khai này sắp xếp theo bất kỳ thuộc tính Comparable nào mà không cần viết closure. Để sắp xếp theo họ, chỉ cần thay đổi KeyPath: \Person.lastName. Nếu cần sắp xếp theo khóa tùy chỉnh (ví dụ: độ dài tên), map được sử dụng bên trong — nhà phát triển vẫn có thể viết closure của riêng mình cho các trường hợp không chuẩn.
AnyKeyPath là kiểu cơ bản trong hệ thống phân cấp KeyPath xóa thông tin về các kiểu gốc và giá trị cụ thể. AnyKeyPath không biết nó được gọi trên kiểu đối tượng nào hoặc trả về kiểu giá trị nào. Điều này hữu ích để lưu trữ KeyPaths không đồng nhất trong bộ sưu tập và cho phản chiếu.
AnyKeyPath được sử dụng khi bạn cần lưu trữ danh sách các đường dẫn đến các thuộc tính khác nhau của các kiểu khác nhau. Ví dụ, trong một trình cấu hình UI, nơi mỗi đường dẫn cấu hình một thuộc tính cụ thể và các kiểu thuộc tính có thể khác nhau. AnyKeyPath cho phép làm việc thống nhất với tất cả các đường dẫn.
protocol Configurable {
func apply(_ keyPath: any PartialKeyPath<Self>, value: any)
}
extension Configurable {
func configure(_ pairs: (any KeyPath<Self, any>, any)...) {
for (path, value) in pairs {
apply(path, value: value)
}
}
}
extension Person: Configurable { }
// Cho phép cấu hình thuộc tính qua AnyKeyPath
AnyKeyPath yêu cầu ép kiểu khi truy xuất giá trị, vì kiểu cụ thể đã bị xóa. Để an toàn kiểu hoàn toàn, KeyPath
KeyPath hiệu quả hơn đáng kể so với khóa chuỗi KVC (value(forKey:)). Khóa chuỗi được giải quyết thông qua cơ chế runtime Objective-C, bao gồm phân tích chuỗi, tra cứu runtime và ép kiểu. KeyPath là cơ chế tĩnh dựa trên cấu trúc Swift gốc.
Theo các điểm chuẩn, KeyPath trung bình nhanh hơn 10-20 lần so với value(forKey:) để truy cập thuộc tính. Sự khác biệt là do không có tra cứu runtime và phân phối động. KeyPath truy cập trực tiếp vào offset của thuộc tính trong bộ nhớ với sự tối ưu hóa đầy đủ của trình biên dịch.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC không chỉ chậm hơn mà còn không an toàn về kiểu: chuỗi “name” có thể không tồn tại và ép kiểu as! String có thể bị lỗi. KeyPath đảm bảo sự tồn tại của thuộc tính và tính đúng đắn của kiểu tại thời điểm biên dịch. Đối với các phần quan trọng về hiệu suất (hoạt ảnh, danh sách hàng nghìn mục), KeyPath là lựa chọn đúng đắn duy nhất.
Câu hỏi thường gặp
KeyPath là một con trỏ đến thuộc tính của đối tượng có thể được truyền như một giá trị thông thường. Thay vì viết person.name, bạn tạo một đường dẫn \Person.name và sử dụng nó để đọc hoặc ghi thuộc tính trên bất kỳ phiên bản Person nào.
KeyPath — chỉ đọc. WritableKeyPath — để đọc và ghi kiểu giá trị (struct) qua ngữ cảnh inout. ReferenceWritableKeyPath — để đọc và ghi kiểu tham chiếu (class) mà không có điều kiện bổ sung.
KeyPath được sử dụng rộng rãi trong Combine (assign(to:on:)), SwiftUI (Binding, FocusState), sắp xếp bộ sưu tập (sorted(by:)), Core Data (NSSortDescriptor) và để truy cập an toàn vào các thuộc tính lồng nhau.
Có, KeyPath hoàn toàn an toàn về kiểu. Lỗi trong đường dẫn (sai chính tả, sai kiểu) được phát hiện tại thời điểm biên dịch, không phải khi chạy. value(forKey:) sử dụng chuỗi và có thể bị lỗi với NSUndefinedKeyException nếu khóa không tồn tại.
Không trực tiếp — Swift không hỗ trợ tạo KeyPath động từ chuỗi. Tuy nhiên, bạn có thể sử dụng AnyKeyPath để lưu trữ và truyền KeyPaths được lấy tĩnh. Để truy cập thuộc tính động bằng chuỗi, hãy sử dụng value(forKey:) với runtime Objective-C.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm