Keypaths trong Swift

Thuật ngữ

keypath: Read-only access tới các thuộc tính.

writablekeypath: read-write access đến các thuộc tính kiểu giá trị.

referencewriteablekeypath: read-write access đến thuộc tính kiểu tham chiếu.

Keypath là gì?

Một keypath cung cấp cách thức truy cập read-only đến một thuộc tính, trong khi một writable keypath thì cung cấp cách thức truy cập writable đến một thuộc tính.
Ví dụ:
Có lẽ cách tốt nhất để mô tả cách truy cập keypath này là thông qua một ví dụ, ở đây chúng ta có một đối tượng kiểu struct:

struct Person {
    var firstname: String
    var secondname: String
    var age: Int
}
let dave = Person(firstname: "Dave", secondname: "Trencher" , age: 21)

Sau đó chúng ta có thể tiếp cận các thuộc tính này thông qua WritableKeyPath<Person, String> hoặc WritableKeyPath<Person, Int> ( Trong đó firstname và secondname là kiểu String và age là kiểu Int).

Kết quả ta có là :

let firstname: String = dave[keyPath: \Person.firstname]
print (firstname) // Dave

Với truy cập WritableKeyPath chúng ta có lưu trữ được các thuộc tính như sau:

var writableKeyPathFirstName: WritableKeyPath<Person, String> = \Person.firstname
print (dave[keyPath: writableKeyPathFirstName]) // Dave

Có nghĩa là chúng ta có tiềm năng sử dụng cùng một thuộc tính ở nhiều nơi khác nhau và lưu trữ chúng như chính một thuộc tính.

Thuộc tính lòng nhau

Sau đây là một ví dụ của những keypaths được lòng vào nhau

struct Socks {
    var sockname: String
}

struct DrawContents {
    var name: String
    var socks: Socks
}

let topdrawer = DrawContents(name: "top", socks: Socks(sockname: "Birthday Socks"))
print (topdrawer)
print (topdrawer[keyPath: \DrawContents.name]) // top
print (topdrawer[keyPath: \DrawContents.socks.sockname]) // Birthday Socks

Thành phần của keypath

Swift cho phép chúng ta linh động phối hợp các keypaths lại tại runtime

let topdrawerkpath = \DrawContents.socks
let sockspath = \Socks.sockname
let composedPath: WritableKeyPath<DrawContents, String> = topdrawerkpath.appending(path: sockspath)

Keypath như kiểu biến có thể xoá Chúng ta có thể sử dụng một keypath trên một kiểu tham chiếu ( ví dụ như class)

let horse = Animal(name: "Keith")
var refKeyPath: ReferenceWritableKeyPath<Animal, String> = \Animal.name
let animalname: String = horse[keyPath: \Animal.name] // Keith

Giới hạn của keypath

Key path không thể tham chiếu đến thành viên tĩnh 'lifeform'

Nếu chúng ta thay đổi Person để có một biến tĩnh.

struct Person {
    static var lifeform = "Carbon"
    var firstname: String
    var secondname: String
    var age: Int
}

dave[keyPath: \Person.lifeform] // Key path cannot refer to static member 'lifeform'

Và..

Nếu chúng ta muốn sử dụng KVO thì sao? Chúng ta sẽ cần phải gọi một keypath khi sử dụng hàm func observe<Value>(_ keyPath: KeyPath<ViewController, Value>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (ViewController, NSKeyValueObservedChange<Value>) -> Void) -> NSKeyValueObservation như sau:

observation = observe(
    \.objectToObserve!.myDate,
    options: [.old, .new]
) { object, change in
    print("myDate changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}

Kết luận

Keypaths thật sự rất hữu dụng trong lập trình iOS cũng như liên quan đến Combine và SwiftUI cũng như KVO.
Mình hi vọng bài viết ngắn này đã chúng chúng ta trở nên quen thuộc với tính năng này trong Swift.

reference: https://stevenpcurtis.medium.com/what-are-swifts-keypaths-e8c829bc97d3


All Rights Reserved