ReSwift 介紹
什么是 ReSwift
ReSwift 是基于 Redux 思想實現的 Swift 類庫。基本的流程如下
當用戶點擊了視圖上的某個元素時,會發出一個 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 。
簡單描述下各個模塊的職責:
View
View 可以理解為一個「殼」,所有的數據都由 State 提供,這樣就把表現層和數據層分開了。
view = f(state)
Action
Action 用來描述發生了什么事情,比如不小心用腳踢到了椅子,神經系統就會把這個信息傳遞給大腦,這個信息就是 Action ,而大腦就是之后要講到的 Store 。
Store
這是核心模塊,就像大腦會不停地接受到各種 Action ,并作出反應,只不過在這里 Store 并不具備「做決定」的能力,而是把這個 Action 交給了所有可能關心它的 Reducers 。
ReSwift 推薦一個 App 只有一個 Store ,在實際情況中,如果這么做的話,會帶來不少的副作用,比如所有的模塊都需要依賴 Store ,這個 State 會很龐大,不可避免的會影響性能。所以,單個頁面或模塊有一個 Store 會比較合適。
State
State 是一個隱形的殺手,因為使用它極其方便,而它的危害也不會瞬間爆發,就像溫水煮青蛙一樣,等發現問題越來越多、被各種多線程問題困擾時,就會感受到它的威力了。
所以把 State 單獨拎出來,并且使用 Value Types 來解決各種多線程或變量被修改導致的問題。
WWDC 的 Protocol and Value Oriented Programming in UIKit Apps 中也推薦使用 Value Composition,而不是繼承,同時把 State 集中到一個地方處理,也有助于 Local Reasoning。
為什么要使用 ReSwift
確切說來是為什么要使用「單向數據流」的架構模式,主要有這么幾個好處:
- 數據單向流動容易讓結構變得清晰,出問題時也更容易排查。
- 使用了 「Value Types」作為流動的數據,避免各種詭異的「不小心被篡改」或多線程 bug。
- 在統一的入口處理數據(State),比起散落在各處更加容易控制。
Readme 里帶了一個簡單的 Demo,可以感受下。
源碼一瞥
ReSwift (3.0.0) 的源碼很精簡,對 Swift 熟悉的話,很快就能看完。說下我自己在看源碼的過程中學到的一些 tips 吧。
Reduce 的使用
reduce 在函數式編程的領域里會經常被用到,甚至可以實現 map / filter 等功能,足見其強大。它的運行規則是以函數的處理結果作為初始值,再結合數組中的元素返回處理結果,不斷循環,直到數組中的元素全部處理完成。
在 Swift 中,它是 Sequence 協議擴展的一個方法,簽名如下
public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Self.Iterator.Element) throws -> Result) rethrows -> Result
在 ReSwift 中有好幾個地方都用到了 reduce ,比如通過它來達到 combineReducer 的效果
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)
}!
}
}
按照入隊列的先后,reducer 被依次執行,并且把生成的新的 State 作為下一個循環的初始值傳遞給下一個 reducer。
在處理 middleware 時,也有用到類似的技術,不過那個更加復雜些,涉及到 高階函數 。
裝飾器模式
裝飾器模式簡單來說就是在不改變類/方法原有功能的前提下,提供了一些額外的能力。比較常見的有 validator,客戶端提交的數據要入庫前需要做一下校驗,不通過的話直接返回。在 python 里裝飾器非常常見,比如在一個方法上加一個 @cached 或者 @validate 等 annotation。
在實現 Reducer 時,有用到這個模式:
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>
_handleAction 對 handleAction 做了個校驗,( withSpecificTypes 函數里如果校驗不通過, handleAction 不會被執行),這樣對于使用者,只需繼承 Reducer 實現 handleAction 方法,ReSwift 內部調用時會使用 _handleAction 來做一些校驗。
在 StoreSubscriber 里也有用到類似的技術。
associatedtype 的使用
通過 associatedtype ,可以讓 protocol 使用 generic , Natasha 還寫過一篇關于 PAT 使用的文章 ,里面以寵物小精靈為例,通過 PAT 讓不同的小精靈具備了不同的能力。不過使用了 associatedtype 或 Self 后,就不能作為變量的類型來聲明了,比如 var something: AProtoclWithAssociatedType 這樣編譯器會報錯,具體原因可以參考 這篇文章 ,主要是因為無法指定 Generic 的類型,導致編譯器無法在編譯期間就確定具體的類型,對于強類型語言來說,這是不能接受的。
ReSwift 中,在定義 StoreType 時,有用到 associatedtype
public protocol StoreType {
associatedtype State: StateType
/// Initializes the store with a reducer and an intial state.
init(reducer: AnyReducer, state: State?)
//...
}</code></pre>
在定義 reducer protocol 時,也有用到(也是關聯了 StateType)。
對外只讀,對內可讀寫
在 OC 時代,通常的做法是在 .h 里聲明為 readonly ,然后在 .m 的 class extension 里,將同名的屬性聲明為 readwrite 。
Swift 沒有頭文件的概念,直接一句話搞定 private(set)
struct Subscription<State: StateType> {
private(set) weak var subscriber: AnyStoreSubscriber? = nil
let selector: ((State) -> Any)?
}
subscription 希望外部可以拿到 subscriber,但不要修改它,于是在前面加了 private(set) ,也就是把 set 方法標記為 private。
小結
ReSwift 還是挺值的一試的,一方面是因為單向數據流確實對程序的清晰度有幫助,另一方面 ReSwift 的代碼很簡潔,內部實現比較容易搞明白,這樣即使出問題也比較容易定位。 Realm 上有作者分享的案例,可以參考下。不足嘛肯定也有,比如功能比較簡單,只是做了數據流,缺少 Diff 支持,在做列表更新/刪除時會比較痛苦;如何與 MVVM 等比較成熟的架構有效地結合起來等。
除此之外,由于數據都通過 State 來傳遞,可以在出 bug 時,上傳當時的 state 內容方便定位;還可以基于 State 來做 時光機 。不妨在 Side Project 中嘗試下。
來自:http://limboy.me/tech/2016/12/04/reswift-analyze.html