Katana是一个现代的Swift框架,用于编写可测试且易于推理的iOS应用程序的业务逻辑。Katana受到Redux的强烈启发。
Overview
应用程序状态完全由一个可序列化的数据结构描述,更改状态的唯一方法是调度StateUpdater。AStateUpdater旨在改变状态,并包含所有要做的信息。因为所有更改都是集中的,并且是按严格顺序进行的,所以没有任何需要注意的微妙的竞争条件。
struct CounterState:State {
var counter:Int=0
}
应用程序的状态只能通过状态更新器修改。StateUpdater表示一个导致应用程序状态改变的事件。你通过实现updateState(:)方法来定义状态更新器的行为,该方法基于当前应用程序状态和StateUpdater本身来改变状态。updateState应该是一个纯函数,这意味着它只依赖于输入(即状态和状态更新器本身),而且它没有副作用,比如网络交互。
struct IncrementCounter: StateUpdater {
func updateState(_ state: inout CounterState) {
state.counter += 1
}
}
Store 包含并管理你的整个应用状态。它负责管理被分派的项(例如,刚才提到的状态更新器)。
let store=Store<CounterState,AppDependencies()
store.dispatch(IncrementCounter())
你可以要求store在应用状态的每一个变化时都得到通知。
store.addListener() {
//the app state has changed
}
Side Effects
使用纯函数更新应用程序的状态很好,而且有很多好处。如果应用程序必须处理外部世界(例如,API调用,磁盘文件管理)。对于所有这类操作,Katana提供了Side Effects 的概念。可以使用Side Effects 与应用程序的其他部分交互,然后分发新的状态更新器来更新状态。对于更复杂的情况,您还可以分派其他Side Effects。
struct GenerateRandomNumberFromBackend: SideEffect {
func sideEffect(_ context: SideEffectContext<CounterState, AppDependencies>) throws {
// invokes the `getRandomNumber` method that returns a promise that is fullfilled
// when the number is received. At that point we dispatch a State Updater
// that updates the state
context.dependencies.APIManager
.getRandomNumber()
.then({ randomNumber in context.dispatch(SetCounter(newValue: randomNumber)) })
}
}
struct SetCounter: StateUpdater {
let newValue: Int
func updateState(_ state: inout CounterState) {
state.counter = self.newValue
}
}
此外,您可以利用await操作符来编写模仿Async / Await模式的逻辑,该逻辑允许您以同步方式编写异步代码。
struct GenerateRandomNumberFromBackend: SideEffect {
func sideEffect(_ context: SideEffectContext<CounterState, AppDependencies>) throws {
// invokes the `getRandomNumber` method that returns a promise that is fullfilled
// when the number is received.
let promise = context.dependencies.APIManager.getRandomNumber()
// we use await to wait for the promise to be fullfilled
let randomNumber = try await(promise)
// then the state is updated using the proper state updater
try await(context.dispatch(SetCounter(newValue: randomNumber)))
}
}
Dependencies(依赖关系)
Side Effects示例利用了一个APIManager方法。Side Effects是可以通过使用上下文的dependencies参数来获取APIManager(context.dependencies.APIManager)。依赖容器是执行依赖注入的Katana。我们测试我们的副作用,因此我们需要摆脱单例或其他阻止我们编写测试的坏习惯。创建依赖项容器非常简单:只需创建一个符合SideEffectDependencyContainer协议的类,使存储对它具有通用性,并在Side Effects中使用它。
final class AppDependencies: SideEffectDependencyContainer {
required init(dispatch: @escaping PromisableStoreDispatch, getState: @escaping GetState) {
// initialize your dependencies here
}
}
Interceptors
当定义一个存储时,你可以提供一个拦截器列表,当一个项目被分派时,这些拦截器会按照给定的顺序被触发。拦截器就像一个包罗万象的系统,可以用来实现日志记录或动态更改存储的行为等功能。每次要处理可分派项时都会调用拦截器。
Katana自带一个内置的dispatchabllogger拦截器,它记录所有的dispatchables,除了黑名单参数中列出的那些。
let dispatchableLogger = DispatchableLogger.interceptor(blackList: [NotToLog.self])
let store = Store<CounterState>(interceptor: [dispatchableLogger])
有时,监听系统中发生的事件并对它们作出反应是很有用的。Katana提供了可以用来实现这个结果的Observer Interceptor。
特别是,您会在以下情况下指示拦截器调度项目:
Store已初始化
状态以特定方式变化
特定的可调度物料由Store管理
特定的通知将发送到默认的NotificationCenter
let observerInterceptor = ObserverInterceptor.observe([
.onStart([
// list of dispatchable items dispatched when the store is initialized
])
])
let store = Store<CounterState>(interceptor: [observerInterceptor])
注意,当使用ObserverInterceptor拦截Side Effects时,dispatchable的返回值对拦截器本身是不可用的。
Requirements
iOS 9.0+ / macOS 10.10+
Xcode 8.0+
Swift 4.0+
CocoaPods
use_frameworks!
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
target 'MyApp' do
pod 'Katana'
end
网友评论