Chắc hẳn chúng ta đã từng phải viết những đoạn code dài dòng kiểu như thế này chỉ để lấy được 1 giá trị cuối cùng ở cuối 1 chuỗi chaining:
apiManager.configuration.networkConfiguration.baseHeaders
apiManager.configuration.networkConfiguration.baseURL
Việc này có 1 vài hạn chế:
- Quá dài chỉ để lấy ra 1 vài giá trị, chưa kể nó bị lặp lại ở nhiều nơi
- Không set
private
choconfiguration
được vì như vậy consumer của API sẽ k lấy được giá trị củabaseHeaders
/baseURL
. - Leak detail implementation cho consumer của API.
Để giải quyết vấn đề này, ta có thể sử dụng Dynamic Member Lookup
Table of contents
- Giới thiệu
- Dynamic Member Lookup với ExpressibleByStringLiteral
- Dynamic Member Lookup với KeyPath
- Kết luận
Giới thiệu
Apply this attribute to a class, structure, enumeration, or protocol to enable members to be looked up by name at runtime.
By Apple
Sử dụng Dynamic Member Lookup bằng cách:
- Sử dụng @dynamicMemberLookup attribute cho class/struct/enum/protocol bạn muốn support
- Khai báo subscript subscript(dynamicMember:). Kiểu dữ liệu của
dynamicMember
có thể là KeyPath, hoặc 1 kiểu dữ liệu conformExpressibleByStringLiteral
Dynamic Member Lookup với ExpressibleByStringLiteral

- Dòng @dynamicMemberLookup đánh dấu struct User để cho biết rằng nó hỗ trợ tính năng dynamicMemberLookup.
- Phương thức subscript được sử dụng để xử lý truy cập vào thành viên của đối tượng. Tham số dynamicMember đại diện cho tên thành viên được truy cập, và phương thức sẽ trả về một chuỗi đơn giản là "Tuan" để minh họa cho việc truy cập thành viên.
- user.name được truy cập, sử dụng cú pháp giống như truy cập vào một thuộc tính của đối tượng. Khi đó, phương thức subscript của struct User được gọi để xử lý việc truy cập thành viên và trả về chuỗi "Tuan".
Chúng ta cũng có thể implement nhiều subscript(dynamicMember) khác nhau cho cùng 1 đối tượng support dynamicMemberLookup

Tuy nhiên, đừng quá lạm dụng sử dụng subscript(dynamicMember:) với ExpressibleByStringLiteral
. Nó có 1 vài nhược điểm:
- Swift tất nhiên sẽ không gợi í cho consumer của class là phải gọi
name
/age
để lấy thông tin. Chúng ta sẽ phải bảo với consumer là phải dùngdynamicMemberLookup
để lấy thông tin => Bad API design. - Làm mất tính safety của Swift.
Having to be aware of internal implementation details is usually a bad sign when it comes to API design
Dynamic Member Lookup với KeyPath
Sử dụng Dynamic Member Lookup với KeyPath là 1 sự lựa chọn tuyệt vời khi nó vẫn giữ được tính safety của Swift, đồng thời vẫn có recommend cho user.
@dynamicMemberLookup
struct APIManager {
private var configuration: Configuration
init(configuration: Configuration) {
self.configuration = configuration
}
subscript<T>(dynamicMember keyPath: KeyPath<NetworkConfiguration, T>) -> T {
return configuration.networkConfiguration[keyPath: keyPath]
}
subscript<T>(dynamicMember keyPath: WritableKeyPath<NetworkConfiguration, T>) -> T {
get {
return configuration.networkConfiguration[keyPath: keyPath]
}
set {
configuration.networkConfiguration[keyPath: keyPath] = newValue
}
}
}
var apiManager = APIManager(
configuration: Configuration(networkConfiguration: .init(
baseURL: URL(string: "https://www.google.com.vn/")!,
baseHeaders: ["key":"someValue"])
)
)
apiManager.baseHeaders
apiManager.baseHeaders["anotherKey"] = "anotherValue"
apiManager.baseHeaders

baseHeaders
và baseURL
cho consumer mà k leak detail implementation của API -> Good API designKết luận
- DynamicMemberLookup được sử dụng rộng rãi trong các thư viện phần mềm như Alamofire, Combine, SwiftUI và nhiều thư viện khác để giảm thiểu việc viết mã boilerplate và tăng tính linh hoạt của ứng dụng.
- Với DynamicMemberLookup, bạn có thể viết mã Swift hiệu quả hơn và tránh việc phải sao chép và dán nhiều mã lặp đi lặp lại.
- DynamicMemberLookup với ExpressibleByStringLiteral giải quyết bài toán khi app cần tương tác với WebView khá tốt, vì ta phải handle javascript code. Đối với các case còn lại, đừng quá lạm dụng DynamicMemberLookup :3
Thanks for reading, guys
Leave a Reply