本文由 Yison 發表在 ScalaCool 團隊博客。html
因 水滴 計劃研發移動端的商家應用,筆者開始了 iOS 端的總體方案設計工做。前端
因爲沒有歷史包袱,且團隊願意嘗試一些不一樣的方案,通過兩週專一的學習和調研以後,咱們並無採用主流的 MVVM 架構,而是基於 ReSwift 以及 Swift 這門語言的特性(核心是 extension)構建了一套相似 Vue + Vuex 的方案,筆者打算經過四篇文章來分享下這種思路。webpack
須要注意的是,筆者也是第一次接觸 Swift 和 iOS,某種程度上來講,也是一名 iOS 菜鳥,行文中不免出現不高明之處,還望指正。但與此同時,筆者也有 Scala 和多年的 Web 前端開發經歷,不一樣的平臺和語言,會有類似的思惟和知識結構,因此入門移動端原生應用開發時,也發現不少共同之處。ios
如下是本系列文章的大綱:git
在介紹 ReSwift 以前,咱們先來簡單回顧下 iOS 端(不嚴謹地說,也能夠當作是移動端應用開發)的架構演變歷史。github
這方面介紹的好文章已經至關的多,重點仍是推薦下 @Bohdan Orlov 的 iOS Architecture Patterns,很是的系統和容易理解。web
在討論架構模式的時候,MVC 是被說起最多的套路之一。衆所周知,Apple 推出的 MVC 跟軟件工程中傳統的 MVC 是不同的。編程
不少人對於經典的 MVC 中的 Model 一直存在誤解,認爲其表明的僅僅只是一個實體模型。其實,它準確的概念應該還包含大量的業務邏輯處理,相對的 Controller 只是在 View 和 Model 層創建一個橋樑而已。redux
注:業界在發展過程當中,圍繞 MVC 也延伸討論了不少的問題,典型的如「胖 Model 和瘦 Model」 的問題,甚至於十幾年前,曾經在 JavaEye 上專門針對 Model 的設計有過一次至關激烈的討論,帖子還在。swift
Apple 的 MVC 採用的是瘦 Model 的設計,ViewController 承載了大量的邏輯處理。之因此這麼設計,也是有緣由的。
若是拿 iOS 平臺和瀏覽器進行對比,它們存在大量可類比的部分,但前者有個很是不同凡響的地方,就是 iOS 和 Android 同樣,都存在很是明顯的生命週期,這些生命週期的方法都存在於 ViewController。
因此最初始的 iOS 架構問題顯而易見:過於臃腫的 View Controller 層大大下降了工程的可維護性以及可測試性。
這裏推薦下 @Krzysztof Zabłocki 的 Good iOS Application Architecture: MVVM vs. MVC vs. VIPER,他不但講述了對不一樣架構的理解,也提出了本身對好架構的評判標準。
解決 Massive View Controller 的一劑良藥來自於 MVP。這種設計思路的核心是提出了一個 Presenter 層,它是鏈接View層與Model層的橋樑並對業務邏輯進行處理,這個符合了咱們理想中的 單一職責原則。
在筆者看來,MVVM 跟 MVP 實際上是十分相似的,這種設計解決了 Massive View Controller 的問題,同時也引入了「雙向數據綁定」,MVVM 也是 Web 前端同窗十分熟悉的概念。
能夠這麼說,MVVM 應該是當下 iOS 以及 Android 最流行的架構設計。
VIPER 是 View + Interactor + Presenter + Entity + Router 的縮寫。對比 Android,這種架構彷佛在 iOS 界更流行,可是總體上而言,採用這種架構的設計並很少。理論上,這是一種很是好的架構思想,靈感於所謂的 The Clean Architecture。
但更細的模塊化設計,也讓 VIPER 被很多人詬病爲一種過分工程。對它感興趣的同窗,能夠看看 objc.io 的 Architecting iOS Apps with VIPER。
在水滴內部,咱們曾採用過 Angular 1.x 開發業務,因此對於「雙向數據綁定」的概念並不陌生。隨着咱們業務的須要,咱們過渡到了更加成熟的 Vue 2 + webpack 來組織 Web 前端的開發。在體驗過不一樣的數據流方案以後,就偏好而言,咱們仍是更加喜好「單向數據流」的套路,緣自於後者設計更簡單,更有利於測試。
因此,在學習了 MVVM 這個成熟的解決方案以後,筆者也開始尋求 iOS 的單向數據流解決方案,後面發現了ReSwift,在通過兩週的體驗和測試,咱們發現這或許是更加符合團隊審美偏好的一種架構設計。
要了解「單向數據流」其實只要學習 Redux 就好了。2014年 Facebook 提出了 Flux 架構的概念,2015年,Redux 出現,將 Flux 與函數式編程結合一塊兒,很短期內就成爲了最熱門的 Web 前端架構。
基於經典的 Redux 模型,ReSwift 也奉行如下設計:
The Store:以單一數據結構管理整個 app 的狀態,狀態只能經過 dispatching Actions 來進行修改。一旦 store 中的狀態改變了,它就會通知給全部的 observers 。
Actions:經過陳述的形式來描述一次狀態變動,它不包含任何代碼,存儲在 store,被轉發給 reducers。reducers 會接收這些 actions 而後進行相應的狀態邏輯變動。
Reducers:基於當前的 action 和 app 狀態,經過純函數來返回一個新的 app 狀態。
筆者發如今當前的 ReSwift 版本中,並無提供 Redux 中相應的 combineReducers 實現。猜測這個其實跟 Swift 與 JavaScript 之間的差別致使,與後者這門動態語言不通,前者存在靜態的類型。但這個問題能夠經過其它辦法來解決。
如今咱們就來看看如何基於 ReSwift 建立一個 iOS 工程。
首先是項目結構設計,假設這是一個多功能的業務需求,看 ReSwift 是否能夠組織一個相對複雜的項目。
import UIKit
import ReSwift
let mainStore = Store<AppState>(
reducer: appReducer,
state: nil
)
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
……
}
複製代碼
import ReSwift
struct AppState: StateType {
var module1State: Module1State
var module2State: Module2State
}
複製代碼
import ReSwift
import ReSwiftRouter
func appReducer(action: Action, state: AppState?) -> AppState {
return AppState(
module1State: module1Reducer(action: action, module1State: state?.module1State),
module2State: module2Reducer(action: action, module2State: state?.module2State)
)
}
複製代碼
import ReSwift
struct Module1State {
……
}
複製代碼
import ReSwift
func module1Reducer(action: Action, module1State: Module1State?) -> Module1State {
return doSomething(module1State) ?? Module1State()
}
複製代碼
import ReSwift
struct Module1Action {
func action1(params: Int) -> Action {
return Action1(params: params)
}
}
extension Module1Action {
struct Action1: Action {
let params: Int
}
}
複製代碼
就這樣,咱們完成了 Redux 相關的結構設計,至於 Redux 跟 ViewController 層如何結合,打交道。咱們將在下一篇關於 Coordinator 的文章中進一步介紹。