KeyPath: khái niệm chính và truy cập kiểu

Tác giả: IT Sectr Đã đăng: 2026-06-18 Thời gian đọc: 10 phút

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 — một đường dẫn kiểu đến thuộc tính, được trình biên dịch xác thực
  • WritableKeyPath — kiểu con của KeyPath cho thuộc tính có quyền ghi
  • ReferenceWritableKeyPath — cho thuộc tính có thể thay đổi của kiểu tham chiếu
  • Combine sử dụng KeyPath để liên kết nhà xuất bản với thuộc tính đối tượng
  • AnyKeyPath — biến thể type-erased để lưu trữ KeyPath trong bộ sưu tập

KeyPath trong Swift là gì?

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.

Cú pháp KeyPath

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.

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

Phân cấp KeyPath: WritableKeyPath và ReferenceWritableKeyPath

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.

Cấp độ truy cập KeyPath

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ểuTruy cậpKiểu đối tượngThay đổi
KeyPathChỉ đọcBất kỳKhông
WritableKeyPathĐọc và ghiKiểu giá trị (inout)Qua ngữ cảnh mutating
ReferenceWritableKeyPathĐọc và ghiKiểu tham chiếuGán trực tiếp
swift
// 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 trong Combine và SwiftUI

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.

Assign trong Combine

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.

swift
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, được tạo qua Binding(get:set:) hoặc phép chiếu ($). KeyPath cho phép SwiftUI tự động theo dõi thay đổi và cập nhật UI mà không cần nỗ lực thêm.

Sử dụng KeyPath để sắp xếp và lọc

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 sắp xếp chung theo KeyPath

Hàm sorted chấp nhận KeyPath trong đó Value: Comparable. Điều này đảm bảo rằng thuộc tính sắp xếp hỗ trợ so sánh. Trình biên dịch kiểm tra điều này tại thời điểm biên dịch.

swift
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 type-erased

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.

Sử dụng AnyKeyPath

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.

swift
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 kiểu luôn được ưu tiên. AnyKeyPath là công cụ cho các trường hợp khi kiểu thực sự không xác định tại thời điểm biên dịch, như tuần tự hóa hoặc phản chiếu.

Hiệu suất KeyPath so với khóa chuỗi

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.

So sánh hiệu suất

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.

swift
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 trong Swift nói một cách đơn giản là gì?

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.

Sự khác biệt giữa KeyPath, WritableKeyPath và ReferenceWritableKeyPath là gì?

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 ở đâu trong Swift?

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.

KeyPath có an toàn hơn value(forKey:) không?

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

Có thể tạo KeyPath động không?

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

  • KeyPath — một đường dẫn kiểu đến thuộc tính, được biểu diễn như kiểu giá trị hạng nhất
  • Phân cấp KeyPath bao gồm WritableKeyPath và ReferenceWritableKeyPath cho các cấp độ truy cập khác nhau
  • Combine và SwiftUI tích cực sử dụng KeyPath cho các liên kết phản ứng
  • Sắp xếp và lọc qua KeyPath cải thiện khả năng đọc mã và an toàn kiểu
  • AnyKeyPath — biến thể type-erased để lưu trữ KeyPaths không đồng nhất trong bộ sưu tập
  • Hiệu suất của KeyPath cao hơn 10-20 lần so với value(forKey:)

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.

Thảo luận dự án

Đọc thêm