Contents
Chào mừng bạn đến với Fx Studio. Chủ đề bài viết lần này là KeyPath được dùng trong iOS & Swift. Hoặc nếu bạn đã từng vọc qua SwiftUI thì nó xuất hiện khá nhiều. Biết thêm về chúng sẽ giúp bạn tự tin hơn khi coding nhóe. Hi vọng bài viết này sẽ giúp ích được cho bạn.
Nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
KeyPath có sẵn trong Swift và cũng được giới thiệu khá là sớm. Do đó, bạn cũng không cần quan tâm tới nhiều về tool & version của các OS. Tuy nhiên, bạn cần nắm qua được các kiến thức lập trình iOS cơ bản trước nhóe.
Còn nếu bạn chưa biết về lập trình iOS, thì có thể bắt đầu bằng series Lập trình iOS cho mọi người.
KeyPath là gì?
Bắt đầu, chúng ta cùng nhau tìm hiểu khái niệm KeyPath trước nhóe. Dể hiểu & đơn giản nhất thì:
KeyPath là một tham chiếu đến một thuộc tính thực tế thay vì một giá trị.
Nói là như vậy thôi, chứ bạn đã dùng nó rất rất nhiều lần rồi, cho dù vô tình hay cố ý đi nữa. Và nếu bạn muốn hiểu rõ hơn, thì hãy tập trung tới thuộc tính thay vì giá trị.
Ví dụ một đoạn code cơ bản nha.
let name = "Fx Studio" let str = "Hello, \(name)" print(str)
Bạn sẽ thấy name
là một biến bình thường và dùng để lưu trữ giá trị. Còn với str
là một biến luôn nhận được giá trị từ biến name
kia. Thay vì xem name
là một đối tượng, hãy xem nó là một thuộc tính của một đối trượng nào đó, thì …
Lúc này, bạn sẽ có một KeyPath liên kết tới thuộc tình của một đối tượng nào đó.
Cuối cùng, KeyPath sẽ nhận giá trị của thuộc tính đó mà không thông qua đối tượng của thuộc tính.
class User { var name: String var gender: Bool var address: Address? init(name: String, gender: Bool) { self.name = name self.gender = gender } } struct Address { let street: String } let user = User(name: "Fx", gender: true) let key = \User.name let userName = user[keyPath: key] print(userName)
Tạm thời, bạn xem qua ví dụ code trên nhoé & thực thi nó để hiểu hơn ý nghĩa tham chiếu tới thuộc tính nhoé.
Cú pháp
Tiếp theo, chúng ta cùng nhau khám phá tiếp cú pháp cho KeyPath là như thế nào nhóe. Về bản chất, bạn sẽ dễ tiếp cận với cú pháp KeyPath mà thôi. Ta xem qua một ví dụ với một thể hiện (object) và cách truy cập tới thuộc tính của nó nhóe.
let gender = user.gender
Trong ví dụ đơn giản trên, ta sử dụng lại đối tượng user
vừa tạo ra và dấu .
để truy cập tới đúng thuộc tính cần lấy giá trị.
Ta sẽ biến tấu thêm một tí, để bạn có một KeyPath tới thuộc tính gender
của lớp User nhóe. Xem tiếp ví dụ nha.
let genderKeyPath = \User.gender
Trong đó, cú pháp sẽ chia ra 2 phần:
- Bắt đầu bằng dấu
\
để chi ra kiểu dữ liệu (TypeName) - Tiếp theo là đường dẫn (path) tới đúng thuộc tính cần lấy.
- Sử dụng các dấu
.
để đi qua các thuộc tính theo các cấp (nếu có).
Với optional, bạn vẫn sử dụng dấu ?
như bình thường nhóe. Ví dụ:
let streetValue = user.address?.street // KeyPath version referencing the same value. let streetKeyPath = \User.address?.street
Như vậy, ta đã biết cách tạo ra một KeyPath rồi nhóe.
Các loại KeyPaths
Chúng ta tiếp tục tìm hiểu về các loại KeyPaths có trong Swift nhóe. Đại khái là bạn sẽ có 5 loại chính. Và chúng được chia ra thành 2 nhóm lớn.
- Read-only key paths
- Writable key paths (có thể đọc & viết)
Nhiều như vậy, nhưng chúng ta sẽ tập trung vào 3 loại chính thôi nhóe.
- KeyPath: Cho phép truy xuất read-only access tới một thuộc tính.
- WritableKeyPath: Cho phép truy xuất read-write tới một thuộc tính.
- ReferenceWritableKeyPath: Chỉ có thể sử dụng với kiểu tham chiếu và cho phép truy xuất read-write tới bất kỳ thuộc tính có thể thay đổi nào.
Phân biệt
Mặc dù có nhiều loại, nhưng bạn chỉ cần quy chúng nó về 2 loại cơ bản là chỉ đọc & có thể ghi mà thôi. Do đó, nhận biết & phân biệt chúng là điều cơ bản đầu tiên mà bạn cần nắm được. Việc phân biệt này cũng khá đơn giản.
- Read-only
- Khi các thuộc tính & subscripts là read-only. Như khai báo là let & subscript là get
- Write-Read
- Với thuộc tính khai báo là var
- Với subscripts khai báo với set/get
- Áp dụng cho cả kiểu tham chiếu & tham trị nhóe
Xem qua ví dụ cho dễ hình dung nhóe!
class User2 { var name: String let email: String init(name: String, email: String) { self.name = name self.email = email } } let name2Key = \User2.name // ReferenceWritableKeyPath<User5, String> let mail2Key = \User2.email // KeyPath<User, String>
Đôi lúc đơn giản là bạn xem thử khai báo là let hay var mà thôi.
Cách sử dụng
Tiếp theo, chúng ta sẽ khám phá thêm các cách dùng KeyPath để đọc & ghi giá trị cho các thuộc tính nhóe. Ta xem qua ví dụ đầu tiên:
let user = User(name: "Fx", gender: true) print(user.name) let nameKeyPath = \User.name // read let name = user[keyPath: nameKeyPath] print(name) // write user3[keyPath: nameKeyPath] = "Fx Studio" print(user.name)
Rất nhanh chóng là bạn sẽ nhận ra cách dùng KeyPath thông qua việc sử dụng subscript(keyPath:)
. Và subscript này có mặt trên tất cả các kiểu dữ liệu. Đối tượng cần đọc & ghi chính là thể hiện của kiểu dữ liệu mà KeyPath đc tạo ra.
Việc đọc & ghi thông qua phép gán với subscript(keyPath:)
. KeyPath chỉ quan tâm tới thuộc tính liên kết tới, chứ không quan tâm tới giá trị. Do đó, khi sử dụng bạn cần phải chú ý các trường hợp lỗi (nil hoặc forced unwrapping optional).
let fourthIndexInteger = \[Int][3] let integers = [0, 1, 2] //print(integers[keyPath: fourthIndexInteger]) // Fatal error: Index out of range
Lỗi sẽ xảy ra lúc runtime, nên sử dụng KeyPath cũng đồng nghĩa với việc không an toàn.
Cuối cùng, bạn sẽ có một đường dẫn đặc biệt. Đó là .self
, nó truy cấp tới một đối tượng, thay vì toàn bộ các thuộc tính. Áp dụng cho tất cả các kiểu dữ liệu.
var foo = "Foo" // 1 let stringIdentity = \String.self // WritableKeyPath<String, String> foo[keyPath: stringIdentity] = "Bar" print(foo) // Bar
Tiện ích
Lợi ích mà KeyPath mang lại còn nhiều hơn việc truy cập đọc & ghi cho các thuộc tính của đối tượng. Mà việc sử dụng nó giúp bạn tăng cường mở rộng việc đọc & ghi hơn nữa. Ta sẽ khám phá tiếp qua các tiện ích sau
Bỏ qua đối tượng
Bạn sẽ thấy đôi lúc chúng ta chỉ cần giá trị của thuộc tính (như: Int, String, Float …) chứ không cần tới một đối tượng nào đó. Nhưng lại bắt buộc cần xác định cụ thể tên (của thuộc tính).
Ví dụ: bạn chỉ cần một biến name
nào đó với kiểu giá trị cho nó là String. Miễn là kiểu dữ liệu bạn sử dụng đảm bảo việc này.
struct Dog { var name: String } struct Cat { var name: String } let dog = Dog(name: "ChiWaWa") let cat = Cat(name: "Kitty") func sayHello(dog: Dog) { print("Hello, \(dog.name)") } sayHello(dog: dog) func sayHello(cat: Cat) { print("Hello, \(cat.name)") } sayHello(cat: cat)
Bạn xem qua ví dụ trên sẽ thấy có khá nhiều thứ lặp lại.
Nếu bạn tưởng tượng function sayHello
cho 100 loại pet khác nhau thì khá là vất vả nhỉ. Do vậy, ta thử xem một cách khác với KeyPath xem sao.
struct PetConfig<Model> { let nameKeyPath: KeyPath<Model, String> func sayHello(for pet: Model) { let temp = pet[keyPath: nameKeyPath] print("Hello, \(temp)") } } let dogConfig = PetConfig<Dog>(nameKeyPath: \.name) dogConfig.sayHello(for: dog) let catConfig = PetConfig<Cat>(nameKeyPath: \.name) catConfig.sayHello(for: cat)
Việc sử dụng này giúp bạn tận dụng được các Model khác nhau và không quan tâm tới thể hiện của các kiểu dữ liệu Model cụ thể nào nữa. Nó khá hữu ích cho việc thiết kế các model & config dùng chung trong project của bạn.
Thay thế Protocol
Chúng ta chuyển sang SwiftUI để khám phá tiếp một tiện ích nữa nhóe. Cái này chúng ta dùng rất là nhiều rồi.
Bạn xem qua ví dụ sau:
struct User: Identifiable { let name: String var id: String { return name } } let users: [User] = [ User(name: "John"), User(name: "Alice"), User(name: "Bob"), ] struct SwiftUIView: View { var body: some View { ScrollView { ForEach(users) { user in Text(user.name) } } } }
Code khá nhiều hỉ.
Bạn chỉ cần để ý tới User conform tới Identifiable Protocol. Điều này giúp cho các đối tượng của User có để định danh được. Nên chúng ta sử dụng được cho ForEach (vì cần xác định mỗi phần tử là duy nhất).
Tuy nhiên, bạn sẽ thấy chúng ta chỉ cần dùng tới user.name
mà thôi. Và bạn thấy thuộc tính name
với kiểu String thì đã conform Identifiable Protocol rồi. Nên việc conform cả kiểu dữ liệu như vậy khá là dư thừa.
Ta tùy chỉnh lại một chút nhóe!
struct User { let name: String } struct SwiftUIView: View { var body: some View { ScrollView { ForEach(users, id: \.name) { user in Text(user.name) } } } }
Bạn không cần conform Protocol nữa, mà chỉ cần chi ra đúng thuộc tính phù hợp. Điều này khá thú vị hơn việc chỉ sử dụng KeyPath để truy cập giá trị thuộc tính.
Sử dụng với hàm
Cuối cùng, chúng ta cùng nhau tìm hiểu tiếp một tiện ích hay nữa. Ta xem qua một ví dụ với function map(_:)
có sẵn cho các array nhóe.
struct User { let name: String let className: String } let users = [ User(name: "John", className: "iOS"), User(name: "Alice", className: "Android"), User(name: "Bob", className: "PHP") ] let userNames = users.map { user in return user.name } // ["John", "Alice", "Bob"]
Bạn sẽ thấy rằng, việc gọi users.map
thì cần truyền một tham số là closure. Trong này, bạn vẫn chỉ cần sử dụng tới 1 thuộc tính mà thôi.
Thay vì sử dụng closure như ở trên ta hoàn toàn có thể sử dụng KeyPath để thay thế. Xem qua tiếp ví dụ nhóe!
extension Array { func map<Value>(_ keyPath: KeyPath<Element, Value>) -> [Value] { return map { $0[keyPath: keyPath] } } } let userNames = users.map(\.name) // ["John", "Alice", "Bob"]
Hoặc ta có thể trích xuất dữ liệu từ bất kỳ một Array nào rất dễ dàng, chẳng hạn với ví dụ trên ta sẽ có.
let userNames = users.map(\.name) let classNames = users.map(\.className)
Lúc này, function sẽ chấp nhận KeyPath là tham số chính. Bạn có thể tùy biến để function trên chấp nhận bất cứ thuộc tính nào của bất cứ kiểu dữ liệu nào. Mà không cần dùng tới một closure để tính toán và return về đúng mục đích muốn sử dụng.
Tạm kết
- Tìm hiểu & phân biệt các loại KeyPath
- Cú pháp và cách sử dụng chúng
- Các tiện ích cơ bản mà nó mang lại, để mở rộng việc đọc & ghi dữ liệu của thuộc tính.
Okay! Tới đây, mình xin kết thúc bài viết về KeyPath trong Swift . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gửi email theo trang Contact.
- Nguồn tham khảo: What is a KeyPath in Swift
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
- Lập trình hướng giao thức (POP) với Swift
You may also like:
Archives
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)