前言
我最喜欢 Swift 语言的一个特性是动态成员查找(dynamic member lookup)。虽然我们并不经常使用它,但它通过改进我们访问特定类型数据的方式,显著改善了所提供类型的 API。 Glassfy:简化构建、管理和推广应用内购买。从订阅管理 SDK 到付费墙等完整的货币化工具。立即免费构建。 基础知识
假设我们正在开发一个提供缓存功能的类型,并将其建模为名为 Cache 的结构体。- struct Cache {
- var storage: [String: Data] = [:]
- }
复制代码 为了访问缓存的数据,我们调用存储属性的下标,该存储属性是 Dictionary 类型提供的。- var cache = Cache()
- let profile = cache.storage["profile"]
复制代码 在这里没有什么特别之处。我们像以前一样通过 Dictionary 类型的下标访问字典。让我们看看如何使用 @dynamicMemberLookup 属性改进 Cache 类型的 API。- @dynamicMemberLookup
- struct Cache {
- private var storage: [String: Data] = [:]
- subscript(dynamicMember key: String) -> Data? {
- storage[key]
- }
- }
复制代码 如上例所示,我们使用 @dynamicMemberLookup 属性标记了 Cache 类型。我们必须实现具有 dynamicMember 参数并返回我们需要的任何内容的下标。- var cache = Cache()
- let profile = cache.profile
复制代码 现在,我们可以更方便地访问 Cache 类型的配置文件数据。我们的 API 的使用者可能会认为配置文件是 Cache 类型的属性,但事实并非如此。
此特性完全在运行时工作,并利用了在点符号后键入的任何属性名称来访问 Cache 类型的下标,该下标具有 dynamicMember 参数。
整个逻辑在运行时运行,编译期间的结果是不确定的。在运行时,您完全可以决定应该从下标返回哪些数据以及如何处理 dynamicMember 参数。
使用 KeyPath 的编译时安全性
我们唯一能找到的缺点是缺乏编译时安全性。我们可以将 Cache 类型视为代码中键入的任何属性名称。幸运的是,@dynamicMemberLookup 下标的参数不仅可以是 String 类型,还可以是 KeyPath 类型。- @dynamicMemberLookup
- final class Store\<State, Action>: ObservableObject {
- typealias ReduceFunction = (State, Action) -> State
- @Published private var state: State
- private let reduce: ReduceFunction
- init(
- initialState state: State,
- reduce: @escaping ReduceFunction
- ) {
- self.state = state
- self.reduce = reduce
- }
- subscript<T>(dynamicMember keyPath: KeyPath<State, T>) -> T {
- state[keyPath: keyPath]
- }
- func send(_ action: Action) {
- state = reduce(state, action)
- }
- }
复制代码 如上例所示,我们定义了接受强类型 KeyPath 实例的 dynamicMember 参数下标。在这种情况下,我们允许 State 类型的 KeyPath,这有助于我们获得编译时安全性。因为每当我们传递与 State 类型无关的错误 KeyPath 时,编译器都会显示错误。- struct State {
- var products: \[String] = \[]
- var isLoading = false
- }
- enum Action {
- case fetch
- }
- let store: Store\<State, Action> = .init(initialState: .init()) { state, action in
- var state = state
- switch action {
- case .fetch:
- state.isLoading = true
- }
- return state
- }
- print(store.isLoading)
- print(store.products)
- print(store.favorites) // Compiler error
复制代码 在上例中,我们通过接受 KeyPath 的下标访问 Store 的私有 state 属性。这看起来与前面的例子类似,但在这种情况下,只要您尝试访问 State 类型的不可用属性,编译器就会显示错误。
总结
今天我们学习了如何使用 @dynamicMemberLookup 属性改进特定类型的 API。虽然并不是每个类型都需要它,但您可以谨慎使用它来改善 API。 |