ReSwift 介绍
haipeng666
8年前
<h3><strong>什么是 ReSwift</strong></h3> <p>ReSwift 是基于 Redux 思想实现的 Swift 类库。基本的流程如下</p> <p><img src="https://simg.open-open.com/show/57e1f3cfe1c063bb9e1abd0bf6118a00.jpg"></p> <p>当用户点击了视图上的某个元素时,会发出一个 Action ,这个 Action 包含了两个基本元素: Action Type 和 Action Payload ,比如「点击收藏按钮」这个 Action ,可能会被描述为: Action("CollectButtonTapped", ["itemID": 189]) 。然后这个 Action 就会到达 Store , Store 也很简单,只做两件事:1. 接收 Action ;2. 将 Action 和 State 发送给 Reducer 。 Reducer 做的事情就更简单了,接收 Store 发出的 Action 和 State ,内部运算之后,返回一个新的 State 。 Store 拿到了新的 State 后,再把 State 发送给 View 。 View 渲染新的 State 。</p> <p>简单描述下各个模块的职责:</p> <p><strong>View</strong></p> <p>View 可以理解为一个「壳」,所有的数据都由 State 提供,这样就把表现层和数据层分开了。</p> <pre> <code class="language-swift">view = f(state)</code></pre> <p><strong>Action</strong></p> <p>Action 用来描述发生了什么事情,比如不小心用脚踢到了椅子,神经系统就会把这个信息传递给大脑,这个信息就是 Action ,而大脑就是之后要讲到的 Store 。</p> <p><strong>Store</strong></p> <p>这是核心模块,就像大脑会不停地接受到各种 Action ,并作出反应,只不过在这里 Store 并不具备「做决定」的能力,而是把这个 Action 交给了所有可能关心它的 Reducers 。</p> <p>ReSwift 推荐一个 App 只有一个 Store ,在实际情况中,如果这么做的话,会带来不少的副作用,比如所有的模块都需要依赖 Store ,这个 State 会很庞大,不可避免的会影响性能。所以,单个页面或模块有一个 Store 会比较合适。</p> <p>State</p> <p>State 是一个隐形的杀手,因为使用它极其方便,而它的危害也不会瞬间爆发,就像温水煮青蛙一样,等发现问题越来越多、被各种多线程问题困扰时,就会感受到它的威力了。</p> <p>所以把 State 单独拎出来,并且使用 Value Types 来解决各种多线程或变量被修改导致的问题。</p> <p>WWDC 的 Protocol and Value Oriented Programming in UIKit Apps 中也推荐使用 Value Composition,而不是继承,同时把 State 集中到一个地方处理,也有助于 Local Reasoning。</p> <h3><strong>为什么要使用 ReSwift</strong></h3> <p>确切说来是为什么要使用「单向数据流」的架构模式,主要有这么几个好处:</p> <ol> <li>数据单向流动容易让结构变得清晰,出问题时也更容易排查。</li> <li>使用了 「Value Types」作为流动的数据,避免各种诡异的「不小心被篡改」或多线程 bug。</li> <li>在统一的入口处理数据(State),比起散落在各处更加容易控制。</li> </ol> <p>Readme 里带了一个简单的 Demo,可以感受下。</p> <h3><strong>源码一瞥</strong></h3> <p>ReSwift (3.0.0) 的源码很精简,对 Swift 熟悉的话,很快就能看完。说下我自己在看源码的过程中学到的一些 tips 吧。</p> <p>Reduce 的使用</p> <p>reduce 在函数式编程的领域里会经常被用到,甚至可以实现 map / filter 等功能,足见其强大。它的运行规则是以函数的处理结果作为初始值,再结合数组中的元素返回处理结果,不断循环,直到数组中的元素全部处理完成。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3f46318529d15b3db3a11a8268ebbd14.png"></p> <p>在 Swift 中,它是 Sequence 协议扩展的一个方法,签名如下</p> <pre> <code class="language-swift">public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Self.Iterator.Element) throws -> Result) rethrows -> Result</code></pre> <p>在 ReSwift 中有好几个地方都用到了 reduce ,比如通过它来达到 combineReducer 的效果</p> <pre> <code class="language-swift">public struct CombinedReducer: AnyReducer { // self.reducers 包含了 AnyReducer 的实例 public func _handleAction(action: Action, state: StateType?) -> StateType { return reducers.reduce(state) { (currentState, reducer) -> StateType in return reducer._handleAction(action: action, state: currentState) }! } }</code></pre> <p>按照入队列的先后,reducer 被依次执行,并且把生成的新的 State 作为下一个循环的初始值传递给下一个 reducer。</p> <p>在处理 middleware 时,也有用到类似的技术,不过那个更加复杂些,涉及到 高阶函数 。</p> <p>装饰器模式</p> <p>装饰器模式简单来说就是在不改变类/方法原有功能的前提下,提供了一些额外的能力。比较常见的有 validator,客户端提交的数据要入库前需要做一下校验,不通过的话直接返回。在 python 里装饰器非常常见,比如在一个方法上加一个 @cached 或者 @validate 等 annotation。</p> <p>在实现 Reducer 时,有用到这个模式:</p> <pre> <code class="language-swift">public protocol AnyReducer { func _handleAction(action: Action, state: StateType?) -> StateType } public protocol Reducer: AnyReducer { associatedtype ReducerStateType func handleAction(action: Action, state: ReducerStateType?) -> ReducerStateType } extension Reducer { public func _handleAction(action: Action, state: StateType?) -> StateType { return withSpecificTypes(action, state: state, function: handleAction) } }</code></pre> <p>_handleAction 对 handleAction 做了个校验,( withSpecificTypes 函数里如果校验不通过, handleAction 不会被执行),这样对于使用者,只需继承 Reducer 实现 handleAction 方法,ReSwift 内部调用时会使用 _handleAction 来做一些校验。</p> <p>在 StoreSubscriber 里也有用到类似的技术。</p> <p>associatedtype 的使用</p> <p>通过 associatedtype ,可以让 protocol 使用 generic , Natasha 还写过一篇关于 PAT 使用的文章 ,里面以宠物小精灵为例,通过 PAT 让不同的小精灵具备了不同的能力。不过使用了 associatedtype 或 Self 后,就不能作为变量的类型来声明了,比如 var something: AProtoclWithAssociatedType 这样编译器会报错,具体原因可以参考 这篇文章 ,主要是因为无法指定 Generic 的类型,导致编译器无法在编译期间就确定具体的类型,对于强类型语言来说,这是不能接受的。</p> <p>ReSwift 中,在定义 StoreType 时,有用到 associatedtype</p> <pre> <code class="language-swift">public protocol StoreType { associatedtype State: StateType /// Initializes the store with a reducer and an intial state. init(reducer: AnyReducer, state: State?) //... }</code></pre> <p>在定义 reducer protocol 时,也有用到(也是关联了 StateType)。</p> <p>对外只读,对内可读写</p> <p>在 OC 时代,通常的做法是在 .h 里声明为 readonly ,然后在 .m 的 class extension 里,将同名的属性声明为 readwrite 。</p> <p>Swift 没有头文件的概念,直接一句话搞定 private(set)</p> <pre> <code class="language-swift">struct Subscription<State: StateType> { private(set) weak var subscriber: AnyStoreSubscriber? = nil let selector: ((State) -> Any)? }</code></pre> <p>subscription 希望外部可以拿到 subscriber,但不要修改它,于是在前面加了 private(set) ,也就是把 set 方法标记为 private。</p> <h3><strong>小结</strong></h3> <p>ReSwift 还是挺值的一试的,一方面是因为单向数据流确实对程序的清晰度有帮助,另一方面 ReSwift 的代码很简洁,内部实现比较容易搞明白,这样即使出问题也比较容易定位。 Realm 上有作者分享的案例,可以参考下。不足嘛肯定也有,比如功能比较简单,只是做了数据流,缺少 Diff 支持,在做列表更新/删除时会比较痛苦;如何与 MVVM 等比较成熟的架构有效地结合起来等。</p> <p>除此之外,由于数据都通过 State 来传递,可以在出 bug 时,上传当时的 state 内容方便定位;还可以基于 State 来做 时光机 。不妨在 Side Project 中尝试下。</p> <p> </p> <p> </p> <p>来自:http://limboy.me/tech/2016/12/04/reswift-analyze.html</p> <p> </p>