Dynamic Member Lookup in Swift

by BackToTheGame
133 views

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 cho configuration được vì như vậy consumer của API sẽ k lấy được giá trị của baseHeaders/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 conform ExpressibleByStringLiteral

Dynamic Member Lookup với ExpressibleByStringLiteral

  1. Dòng @dynamicMemberLookup đánh dấu struct User để cho biết rằng nó hỗ trợ tính năng dynamicMemberLookup.
  2. 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.
  3. 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ùng dynamicMemberLookup để 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
Bạn có thể thấy, Swift vẫn recommend sử dụng baseHeadersbaseURL cho consumer mà k leak detail implementation của API -> Good API design

Kế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 Comment

* By using this form you agree with the storage and handling of your data by this website.