用 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 的文章中进一步介绍。

相关文章
相关标签/搜索