用 Swift 模仿 Vue + Vuex 進行 iOS 開發(一):ReSwift

本文由 Yison 發表在 ScalaCool 團隊博客。html

水滴 計劃研發移動端的商家應用,筆者開始了 iOS 端的總體方案設計工做。前端

因爲沒有歷史包袱,且團隊願意嘗試一些不一樣的方案,通過兩週專一的學習和調研以後,咱們並無採用主流的 MVVM 架構,而是基於 ReSwift 以及 Swift 這門語言的特性(核心是 extension)構建了一套相似 Vue + Vuex 的方案,筆者打算經過四篇文章來分享下這種思路。webpack

須要注意的是,筆者也是第一次接觸 Swift 和 iOS,某種程度上來講,也是一名 iOS 菜鳥,行文中不免出現不高明之處,還望指正。但與此同時,筆者也有 Scala 和多年的 Web 前端開發經歷,不一樣的平臺和語言,會有類似的思惟和知識結構,因此入門移動端原生應用開發時,也發現不少共同之處。ios

如下是本系列文章的大綱:git

  1. ReSwift
  2. Coordinator
  3. extension
  4. VueLike

架構方式的演變

在介紹 ReSwift 以前,咱們先來簡單回顧下 iOS 端(不嚴謹地說,也能夠當作是移動端應用開發)的架構演變歷史。github

這方面介紹的好文章已經至關的多,重點仍是推薦下 @Bohdan Orlov 的 iOS Architecture Patterns,很是的系統和容易理解。web

Massive View Controller

在討論架構模式的時候,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,他不但講述了對不一樣架構的理解,也提出了本身對好架構的評判標準。

MVP

解決 Massive View Controller 的一劑良藥來自於 MVP。這種設計思路的核心是提出了一個 Presenter 層,它是鏈接View層與Model層的橋樑並對業務邏輯進行處理,這個符合了咱們理想中的 單一職責原則

MVVM

在筆者看來,MVVM 跟 MVP 實際上是十分相似的,這種設計解決了 Massive View Controller 的問題,同時也引入了「雙向數據綁定」,MVVM 也是 Web 前端同窗十分熟悉的概念。

能夠這麼說,MVVM 應該是當下 iOS 以及 Android 最流行的架構設計。

VIPER

VIPER 是 View + Interactor + Presenter + Entity + Router 的縮寫。對比 Android,這種架構彷佛在 iOS 界更流行,可是總體上而言,採用這種架構的設計並很少。理論上,這是一種很是好的架構思想,靈感於所謂的 The Clean Architecture

但更細的模塊化設計,也讓 VIPER 被很多人詬病爲一種過分工程。對它感興趣的同窗,能夠看看 objc.io 的 Architecting iOS Apps with VIPER

ReSwift

在水滴內部,咱們曾採用過 Angular 1.x 開發業務,因此對於「雙向數據綁定」的概念並不陌生。隨着咱們業務的須要,咱們過渡到了更加成熟的 Vue 2 + webpack 來組織 Web 前端的開發。在體驗過不一樣的數據流方案以後,就偏好而言,咱們仍是更加喜好「單向數據流」的套路,緣自於後者設計更簡單,更有利於測試。

因此,在學習了 MVVM 這個成熟的解決方案以後,筆者也開始尋求 iOS 的單向數據流解決方案,後面發現了ReSwift,在通過兩週的體驗和測試,咱們發現這或許是更加符合團隊審美偏好的一種架構設計。

Redux

要了解「單向數據流」其實只要學習 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 狀態。

combineReducers

筆者發如今當前的 ReSwift 版本中,並無提供 Redux 中相應的 combineReducers 實現。猜測這個其實跟 Swift 與 JavaScript 之間的差別致使,與後者這門動態語言不通,前者存在靜態的類型。但這個問題能夠經過其它辦法來解決。

牛刀小試

如今咱們就來看看如何基於 ReSwift 建立一個 iOS 工程。

首先是項目結構設計,假設這是一個多功能的業務需求,看 ReSwift 是否能夠組織一個相對複雜的項目。

項目結構

  • App
    • AppReducer.swift
    • AppState.swift
  • Modules
    • Module1
      • Actions
      • Reducers
      • State
    • Module2
      • ……
  • Views
  • AppDelegte.swift
  • ……

AppDelegate.swift

import UIKit
import ReSwift

let mainStore = Store<AppState>(
    reducer: appReducer,
    state: nil
)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    ……
}

複製代碼

App/AppState.swift

import ReSwift

struct AppState: StateType {
    var module1State: Module1State
    var module2State: Module2State
}
複製代碼

App/AppReducer.swift

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)
    )
}

複製代碼

Modules/Module1/State/Module1State.swift

import ReSwift

struct Module1State {
  ……
}
複製代碼

Modules/Module1/Reducers/Module1Reducer.swift

import ReSwift

func module1Reducer(action: Action, module1State: Module1State?) -> Module1State {
    return doSomething(module1State) ?? Module1State()
}
複製代碼

Modules/Module1/Actions/Module1Action.swift

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 的文章中進一步介紹。

相關文章
相關標籤/搜索