前言

我最喜欢 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。