@Access
宏旨在简化您 Swift 类型的细粒度访问级别控制。
@Access(emit: .fileprivate)
enum Action {
case didStart
case didFinish
}
// generates ⬇️
public struct ActionAccessor {
let value: Action
fileprivate init(_ value: Action) {
self.value = value
}
fileprivate static let didStart = Self(.didStart)
fileprivate static let didFinish = Self(.didFinish)
}
在 The Composable Architecture 中,有一些关于 action 边界的讨论
想法是我们想要避免创建一个包含所有 action 的扁平 Action
枚举,而是选择一个更嵌套的结构
public enum Action {
public enum Public: Equatable {
case load(URL)
}
public enum Delegate: Equatable {
case didFinishLoading
}
public enum Internal {
case progressChanged(Double)
case loadingFinished
}
case `public`(Public)
case delegate(Delegate)
case `internal`(Internal)
}
这样我们就可以对可理解的 action 子集编写详尽的 switch 语句,而不是退回到 default
。
我看到的这种方法的问题是,它本身并不能防止误用,任何 action 仍然可以在任何地方发出和读取,只是更难意外地做到这一点。通过自定义 lint 规则可以实现更好的情况,但我相信类型系统可以被利用来获得更好的解决方案。
@Access
宏创建一个 public struct
,包装带注解的类型,并允许您分别指定 read
和 emit
的访问级别。read
影响应用程序的哪个部分可以读取类型的实际值(例如,对 action 进行 switch
)。emit
影响应用程序的哪个部分可以创建类型的实例。请参阅改进后的先前示例
public enum Action {
// Public action can be created anywhere, can be read only in the file scope
@Access(read: .fileprivate)
public enum Public: Equatable {
case load(URL)
}
// Delegate action can only be created in the file scope, but can be
// accessed anywhere
@Access(emit: .fileprivate)
public enum Delegate: Equatable {
case didFinishLoading
}
// Fileprivate action inherits `fileprivate` modifier for both reading and emitting,
// forbidding both outside of the file scope, but still letting the action
// be a part of a public enum
@Access
fileprivate enum Fileprivate {
case progressChanged(Double)
case loadingFinished
}
case `public`(PublicAccessor)
case delegate(DelegateAccessor)
case `fileprivate`(FileprivateAccessor)
}
通过将 action 声明放在与 TCA Reducer
相同的文件中,我们可以限制 Fileprivate
action 仅在该文件中可见,同时允许父 reducer 读取 Delegate
action 并发出 Public
action,禁止其余的。这种方法的缺点是整个 action 无法使用单个 switch
进行切换,并且需要在每个 action.value
上使用单独的 switch
语句,但这可能有利于确保更少的 catch all default
语句,并且也可以通过对 Equatable
类型使用生成的 is
函数来缓解。
请参阅 AccessMacroClient 和 AccessTests 以获取更易读的示例。
let value
和 init(value)
具有单独的访问级别case didStart
// of a wrapped type
// yields
fileprivate static let didStart = Self(.didStart)
// on a wrapper, keeping the action creation syntax intact
case didStart(at: Date)
// of a wrapped type
// yields
fileprivate static func didStart(at: Date) -> Self {
return Self(.didStart(at: at))
}
// on a wrapper, keeping the action creation syntax intact
Equatable
和 Hashable
的一致性is
函数,以与 switch 内部的 where
结合使用value