基于ReSwift和App Coordinator的iOS架構(gòu)

iOS架構(gòu)漫談
當(dāng)我們?cè)谡刬OS應(yīng)用架構(gòu)時(shí)秋忙,我們聽(tīng)到最多的是MVC,MVVM境肾,VIPER這三個(gè)Buzz Word,他們的邏輯一脈相承胆屿,不斷的從ViewController中把邏輯拆分出去奥喻。從蘋果官方推薦的MVC:


圖片來(lái)源
隨著系統(tǒng)的復(fù)雜,把功能進(jìn)行細(xì)化非迹,把整合View展示數(shù)據(jù)的邏輯的獨(dú)立出來(lái)形成ViewModel模塊环鲤,架構(gòu)風(fēng)格就變成了MVVM:

圖片來(lái)源
隨著系統(tǒng)的更加復(fù)雜,把路由的職責(zé)憎兽,獲取數(shù)據(jù)的職責(zé)也獨(dú)立出去冷离,架構(gòu)風(fēng)格就變成了VIPER:

圖片來(lái)源
本文則想從另一個(gè)角度和大家探討一個(gè)新的iOS應(yīng)用架構(gòu)方案,架構(gòu)的本質(zhì)是管理復(fù)雜性纯命,在討論具體的架構(gòu)方案前西剥,我們首先應(yīng)該明確一個(gè)iOS應(yīng)用的開(kāi)發(fā),其復(fù)雜性在哪里?
iOS應(yīng)用的開(kāi)發(fā)復(fù)雜度
對(duì)于一個(gè)iOS應(yīng)用來(lái)說(shuō)亿汞,其開(kāi)發(fā)的復(fù)雜性主要體現(xiàn)在三個(gè)方面:
復(fù)雜界面設(shè)計(jì)的實(shí)現(xiàn)和樣式管理
iOS App最終呈現(xiàn)給用戶的是一組組的UI界面瞭空,而對(duì)于一個(gè)特定的App來(lái)說(shuō),其UI的設(shè)計(jì)元素(如配色,字體大小咆畏,間距等)基本上是固定的南捂,另外,組成該App的基礎(chǔ)組件(如Button種類旧找,輸入框種類等)也是有限的溺健。但是如何管理,組合钮蛛,重用組件則是架構(gòu)師需要考慮的問(wèn)題鞭缭,尤其是一些App在開(kāi)發(fā)過(guò)程中可能出現(xiàn)大量的UI樣式重構(gòu),更需要清晰的控制住重構(gòu)的影響范圍魏颓。這兒的復(fù)雜性本質(zhì)上是UI組件自身設(shè)計(jì)實(shí)現(xiàn)的復(fù)雜性岭辣,多UI組件之間的組合方式和UI組件的重用機(jī)制。
路由設(shè)計(jì)
對(duì)于一個(gè)大型的iOS應(yīng)用琼开,通常會(huì)把其功能按Feature拆分易结,經(jīng)過(guò)這樣的拆分之后,其可能出現(xiàn)的路由有以下幾種:

APP間路由: 從其它App調(diào)起當(dāng)前App柜候,并進(jìn)入一個(gè)很深層次的頁(yè)面(圖示1)搞动。

APP內(nèi)路由:

啟動(dòng)進(jìn)入App的Home頁(yè)面(圖示2)
從Home頁(yè)面到進(jìn)Feature Flow(圖示3)
Feature內(nèi)按流程的頁(yè)面的路由(圖示4)
各Feature之間的頁(yè)面跳轉(zhuǎn)(圖示5)
各Feature共享的單點(diǎn)信息頁(yè)的跳轉(zhuǎn)(圖示6)

根據(jù)Apple官方的MVC架構(gòu),這些復(fù)雜的各種跳轉(zhuǎn)邏輯渣刷,以及跳轉(zhuǎn)前的ViewController的準(zhǔn)備工作等邏輯纏繞在AppDelegate的初始化鹦肿,ViewController的UI邏輯中。這兒的復(fù)雜性主要是UI和業(yè)務(wù)之間纏繞不清的相互耦合辅柴。
應(yīng)用狀態(tài)管理
一個(gè)iOS應(yīng)用本質(zhì)上就是一個(gè)狀態(tài)機(jī)箩溃,從一個(gè)狀態(tài)的UI由User Action或者API調(diào)用返回的Data Action觸發(fā)達(dá)到下一個(gè)狀態(tài)的UI。為了準(zhǔn)確的控制應(yīng)用功能碌嘀,開(kāi)發(fā)者需要能夠清楚的知道:
應(yīng)用的當(dāng)前UI是由哪些狀態(tài)決定的涣旨?
User Action會(huì)影響哪些應(yīng)用狀態(tài)?如何影響的股冗?
Data Action會(huì)影響哪些應(yīng)用狀態(tài)霹陡?如何影響的?

在MVC止状,MVVM烹棉,VIPER的架構(gòu)中,應(yīng)用的狀態(tài)分散在Model或者Entity中怯疤,甚至有些狀態(tài)直接保存在View Controller中浆洗,在跟蹤狀態(tài)時(shí)經(jīng)常需要跨越多個(gè)Model,很難獲取到一個(gè)全貌的應(yīng)用狀態(tài)集峦。另外伏社,對(duì)于Action會(huì)如何影響應(yīng)用的狀態(tài)跟蹤起來(lái)也比較困難抠刺,尤其是當(dāng)一個(gè)Action產(chǎn)生的影響路徑不同,或最終可能導(dǎo)致多個(gè)Model的狀態(tài)發(fā)生改變時(shí)洛口。這兒的復(fù)雜性主要體現(xiàn)在治理分散的狀態(tài)矫付,以及管理不統(tǒng)一的狀態(tài)改變機(jī)制帶來(lái)的復(fù)雜性凯沪。
如何管理這些復(fù)雜度
前面明確了iOS應(yīng)用開(kāi)發(fā)的復(fù)雜性所在第焰,那么從架構(gòu)層面上應(yīng)該如何去管理這些復(fù)雜性呢?
使用Atomic Design和Component Driven Development管理界面開(kāi)發(fā)的復(fù)雜度
UI界面的復(fù)雜度本質(zhì)上是一個(gè)點(diǎn)上的復(fù)雜度,其復(fù)雜性集中在系統(tǒng)的某些小細(xì)節(jié)處妨马,不會(huì)增加系統(tǒng)整體規(guī)劃的復(fù)雜度挺举,所以控制其復(fù)雜度的主要方式是隔離,避免一個(gè)UI組件之間的相互交織烘跺,變成一個(gè)面上的復(fù)雜度湘纵,導(dǎo)致復(fù)雜度不可控。在UI層滤淳,最流行的隔離方式就是組件化梧喷,在筆者之前的一篇文章《前端組件化方案》中詳細(xì)解釋了前端組件化方案的實(shí)施細(xì)節(jié),這兒就不再贅述脖咐。
使用App Coordinator統(tǒng)一管理應(yīng)用路由
應(yīng)用的路由主要分為App間路由和App內(nèi)路由铺敌,對(duì)它們需要分別處理
App間路由
對(duì)于APP之間的路由,主要通過(guò)兩種方式實(shí)現(xiàn):
一種是URL Scheme 通過(guò)在當(dāng)前App中配置進(jìn)行相應(yīng)的設(shè)置屁擅,即可從別的APP跳轉(zhuǎn)到當(dāng)前APP偿凭。進(jìn)入當(dāng)前App之后,直接在AppDelegate中的方法:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

轉(zhuǎn)換進(jìn)App內(nèi)的路由派歌。
另一種是Universal Links弯囊,同樣的通過(guò)在當(dāng)前App中進(jìn)行配置,當(dāng)用戶點(diǎn)擊URL就會(huì)跳轉(zhuǎn)到當(dāng)前的App里胶果。進(jìn)入當(dāng)前APP之后匾嘱,直接在AppDelegate中的方法:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool

中轉(zhuǎn)進(jìn)App內(nèi)路由。
所以App間的路由邏輯相對(duì)簡(jiǎn)單早抠,就是一個(gè)把外部URL映射到內(nèi)部路由中霎烙。這部分只需要增加一個(gè)URL Scheme或Universal Link對(duì)應(yīng)到App內(nèi)路由的處理邏輯即可。
App內(nèi)路由
對(duì)于內(nèi)部路由贝或,我們可以引入App Coordinator來(lái)管理所有路由吼过。App Coordinator是Soroush Khanlou在2015年的NSSpain演講上提出的一個(gè)模式,其本質(zhì)上是Martin Fowler在《Patterns of Enterprise Application Architecture》中描述的Application Controller模式在iOS開(kāi)發(fā)上的應(yīng)用咪奖。其核心理念如下:
抽象出一個(gè)Coordinator對(duì)象概念
由該Coordinator對(duì)象負(fù)責(zé)ViewController的創(chuàng)建和配置盗忱。
由該Coordinator對(duì)象來(lái)管理所有的ViewController跳轉(zhuǎn)
Coordinator可以派生子Coordinator來(lái)管理不同的Feature Flow

經(jīng)過(guò)這層抽象之后,一個(gè)復(fù)雜App的路由對(duì)應(yīng)關(guān)系就會(huì)如下:



從圖中可以看出羊赵,應(yīng)用的UI和業(yè)務(wù)邏輯被清晰的拆分開(kāi)趟佃,各自有了自己清晰的職責(zé)扇谣。ViewController的初始化,ViewController之間的鏈接邏輯全部都轉(zhuǎn)移到App Coordinator的體系中去了闲昭,ViewController則徹底變成了一個(gè)個(gè)獨(dú)立的個(gè)體罐寨,其只負(fù)責(zé):
自己界面內(nèi)的子UIView組織,
接收數(shù)據(jù)并把數(shù)據(jù)綁定到對(duì)應(yīng)的子UIView展示
把界面上的user action轉(zhuǎn)換為業(yè)務(wù)上的user intents序矩,然后轉(zhuǎn)入App Coordinator中進(jìn)行業(yè)務(wù)處理鸯绿。

通過(guò)引入AppCoordinator之后,UI和業(yè)務(wù)邏輯被拆分開(kāi)簸淀,各自處理自己負(fù)責(zé)的邏輯瓶蝴。在iOS應(yīng)用中,路由的底層實(shí)現(xiàn)還是UINavigationController提供的present租幕,push舷手,pop等函數(shù),在其之上劲绪,iOS社區(qū)出了各種封裝庫(kù)來(lái)更好的封裝ViewController之間的跳轉(zhuǎn)接口男窟,如JLRoutesroutable-ios贾富,MGJRouter等歉眷,在這個(gè)基礎(chǔ)上我們來(lái)進(jìn)一步思考App Coordinator,其概念核心是把ViewController跳轉(zhuǎn)和業(yè)務(wù)邏輯一起抽象為user intents(用戶意圖)祷安,對(duì)于開(kāi)發(fā)者具體使用什么樣的方式實(shí)現(xiàn)的跳轉(zhuǎn)邏輯并沒(méi)有限制姥芥,而路由的實(shí)現(xiàn)方式在一個(gè)應(yīng)用中的影響范圍非常廣,切換路由的實(shí)現(xiàn)方式基本上就是一次全App的重構(gòu)(做過(guò)React應(yīng)用的react-router0.13升級(jí)的朋友應(yīng)該深有體會(huì))汇鞭。所以在App Coordinator的基礎(chǔ)之上凉唐,還可以引入Protocol-Oriented Programming的概念,在App Coordinator的具體實(shí)現(xiàn)和ViewController之間抽象一層Protocols霍骄,把UI和業(yè)務(wù)邏輯的實(shí)現(xiàn)徹底抽離開(kāi)台囱。經(jīng)過(guò)這層抽象之后,路由關(guān)系變化如下:


經(jīng)過(guò)App Coordinator統(tǒng)一處理路由之后读整,App可以得到如下好處:
ViewController變得非常簡(jiǎn)單簿训,成為了一個(gè)概念清晰的,獨(dú)立的UI組件米间。這極大的增加了其可復(fù)用性强品。
UI和業(yè)務(wù)邏輯的抽離也增加了業(yè)務(wù)代碼的可復(fù)用性,在多屏?xí)r代屈糊,當(dāng)你需要為當(dāng)前應(yīng)用增加一個(gè)iPad版本時(shí)的榛,只需要重新做一套iPad UI對(duì)接到當(dāng)前iPhone版的App Coordinator中就完成了。
App Coordinator定義與實(shí)現(xiàn)的分離逻锐,UI和業(yè)務(wù)的分離讓應(yīng)用在做A/B Testing時(shí)變得更加容易夫晌,可以簡(jiǎn)單的使用不同實(shí)現(xiàn)的Coordinator雕薪,或者不同版本的ViewController即可。

使用ReSwift管理應(yīng)用狀態(tài)
前面提到引入App Coordinator之后晓淀,ViewController的剩下的職責(zé)之一就是“接收數(shù)據(jù)并把數(shù)據(jù)綁定到對(duì)應(yīng)的子UIView展示”所袁,這兒的數(shù)據(jù)來(lái)源就是應(yīng)用的狀態(tài)。它山之石凶掰,可以攻玉燥爷,不只是iOS應(yīng)用有復(fù)雜狀態(tài)管理的問(wèn)題,在越來(lái)越多的邏輯往前端遷移的時(shí)代锄俄,所有的前端都面臨著類似的問(wèn)題局劲,而目前Web前端最火的Redux就是為了解決這個(gè)問(wèn)題誕生的狀態(tài)管理機(jī)制勺拣,而ReSwift則把這套機(jī)制帶入了iOS的世界奶赠。這套機(jī)制中主要有一下幾個(gè)概念:
App State: 在一個(gè)時(shí)間點(diǎn)上,應(yīng)用的所有狀態(tài). 只要App State一樣药有,應(yīng)用的展現(xiàn)就是一樣的毅戈。
Store: 保存App State的對(duì)象,其還負(fù)責(zé)發(fā)送Action更新App State.
Action: 表示一次改變應(yīng)用狀態(tài)的行為愤惰,其本身可以攜帶用以改變App State的數(shù)據(jù)苇经。
Reducer: 一個(gè)接收當(dāng)前App State和Action,返回新的App State的小函數(shù)宦言。

在這個(gè)機(jī)制下, 一個(gè)App的狀態(tài)轉(zhuǎn)換如下:
啟動(dòng)初始化App State -> 初始化UI扇单,并把它綁定到對(duì)應(yīng)的App State的屬性上
業(yè)務(wù)操作 -> 產(chǎn)生Action -> Reducer接收Action和當(dāng)前App State產(chǎn)生新的AppState -> 更新當(dāng)前State -> 通知UI AppState有更新 -> UI顯示新的狀態(tài) -> 下一個(gè)業(yè)務(wù)操作......

在這個(gè)狀態(tài)轉(zhuǎn)換的過(guò)程中,需要注意奠旺,業(yè)務(wù)操作會(huì)有兩類:
無(wú)異步調(diào)用的操作蜘澜,如點(diǎn)擊界面把界面數(shù)據(jù)存儲(chǔ)到App State上;這類操作處理起來(lái)非常簡(jiǎn)單响疚,按照上面提到的狀態(tài)轉(zhuǎn)換流程走一圈即可鄙信。
有異步調(diào)用的操作。如點(diǎn)擊查詢忿晕,調(diào)用API装诡,數(shù)據(jù)返回之后再存儲(chǔ)到App State上。這類操作就需要引入一個(gè)新的邏輯概念(Action Creators)來(lái)處理践盼,通過(guò)Action Creators來(lái)處理異步調(diào)用并分發(fā)新的Action鸦采。

整個(gè)App的狀態(tài)變換過(guò)程如下:


無(wú)異步調(diào)用操作的狀態(tài)流轉(zhuǎn)(圖片來(lái)源

有異步調(diào)用操作的狀態(tài)流轉(zhuǎn)(圖片來(lái)源
經(jīng)過(guò)ReSwift統(tǒng)一管理應(yīng)用狀態(tài)之后,App開(kāi)發(fā)可以得到如下好處:
統(tǒng)一管理應(yīng)用狀態(tài)咕幻,包括統(tǒng)一的機(jī)制和唯一的狀態(tài)容器渔伯,這讓應(yīng)用狀態(tài)的改變更容易預(yù)測(cè),也更容易調(diào)試谅河。
清晰的邏輯拆分咱旱,清晰的代碼組織方式确丢,讓團(tuán)隊(duì)的協(xié)作更加容易。
函數(shù)式的編程方式吐限,每個(gè)組件都只做一件小事并且是獨(dú)立的小函數(shù)鲜侥,這增加了應(yīng)用的可測(cè)試性。
單向數(shù)據(jù)流诸典,數(shù)據(jù)驅(qū)動(dòng)UI的編程方式描函。

整理后的iOS架構(gòu)
經(jīng)過(guò)上面的大篇幅介紹,下面我們就來(lái)歸納下結(jié)合了App Coordinator和ReSwift的一個(gè)iOS App的整體架構(gòu)圖:


架構(gòu)實(shí)戰(zhàn)
上面已經(jīng)講解了整體的架構(gòu)原理狐粱,"Talk is cheap"舀寓, 接下來(lái)就以Raywendlich上面的這個(gè)App為例來(lái)看看如何實(shí)踐這個(gè)架構(gòu)。

(圖片來(lái)源:https://koenig-media.raywenderlich.com/uploads/2015/03/PropertyFinder.png
第一步:構(gòu)建UI組件
在構(gòu)建UI組件時(shí)肌蜻,因?yàn)槊總€(gè)組件都是獨(dú)立的互墓,所以團(tuán)隊(duì)可以并發(fā)的做多個(gè)UI頁(yè)面,在做頁(yè)面時(shí)蒋搜,需要考慮:
該ViewController包含多少子UIView篡撵?子UIView是如何組織在一起的?
該ViewController需要的數(shù)據(jù)及該數(shù)據(jù)的格式豆挽?
該ViewController需要支持哪些業(yè)務(wù)操作育谬?

以第一個(gè)頁(yè)面為例:

class SearchSceneViewController: BaseViewController { 
//定義業(yè)務(wù)操作的接口  
var searchSceneCoordinator:SearchSceneCoordinatorProtocol? 
//子組件  
var searchView:SearchView? 

//該UI接收的數(shù)據(jù)結(jié)構(gòu)  
private func update(state: AppState) {
    if let searchCriteria = state.property.searchCriter {
        searchView?.update(searchCriteria: searchCriteria) 
    } 
} 

//支持的業(yè)務(wù)操作  
func searchByCity(searchCriteria:SearchCriteria) {   
      searchSceneCoordinator?.searchByCity(searchCriteria: searchCriteria) 
}

func searchByCurrentLocation() {   
      searchSceneCoordinator?.searchByCurrentLocation() 
} 

//子組件的組織  
override func viewDidLoad() { 
      super.viewDidLoad() 
      searchView = SearchView(frame: self.view.bounds)     
      searchView?.goButtonOnClick = self.searchByCity 
      searchView?.locationButtonOnClick = self.searchByCurrentLocation 
    self.view.addSubview(searchView!) 
    }
}

注:子組件支持的操作都以property的形式從外部注入,組件內(nèi)命名更組件化帮哈,不應(yīng)包含業(yè)務(wù)含義膛檀。
其它的幾個(gè)ViewController也依法炮制,完成所有UI組件娘侍,這步完成之后咖刃,我們就有了App的所有UI組件,以及UI支持的所有操作接口私蕾。下一步就是把他們串聯(lián)起來(lái)僵缺,根據(jù)業(yè)務(wù)邏輯完成User Journey。
第二步:構(gòu)建App Coordinators串聯(lián)所有的ViewController
首先踩叭,在AppDelegate中加入AppCoordinator磕潮,把路由跳轉(zhuǎn)的邏輯轉(zhuǎn)移到AppCoordinator中。

var appCoordinator: AppCoordinator!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 
      window = UIWindow() 
      let rootVC = UINavigationController() 
      window?.rootViewController = rootVC 
      appCoordinator = AppCoordinator(rootVC) 
      appCoordinator.start() 
      window?.makeKeyAndVisible() 
      return true 
}

然后容贝,在AppCoordinator中實(shí)現(xiàn)首頁(yè)SeachSceneViewController的加載

class AppCoordinator { 
      var rootVC: UINavigationController 
      
      init(_ rootVC: UINavigationController){ 
          self.rootVC = rootVC
     } 
    
    func start() { 
          let searchVC = SearchSceneViewController()
          let searchSceneCoordinator = SearchSceneCoordinator(self.rootVC) 
          searchVC.searchSceneCoordinator = searchSceneCoordinator     
          self.rootVC.pushViewController(searchVC, animated: true) 
          }
    }

在上一步中我們已經(jīng)為每個(gè)ViewController定義好對(duì)應(yīng)的CoordinatorProtocol拧揽,也會(huì)在這一步中實(shí)現(xiàn)

protocol SearchSceneCoordinatorProtocol { 
     func searchByCity(searchCriteria:SearchCriteria) 
     func searchByCurrentLocation()
}

?class SearchSceneCoordinator: AppCoordinator, SearchSceneCoordinatorProtocol { 
     func searchByCity(searchCriteria:SearchCriteria) {   
         self.pushSearchResultViewController() 
     } 

     func searchByCurrentLocation() {
         self.pushSearchResultViewController() 
     }

    private func pushSearchResultViewController() { 
          let searchResultVC = SearchResultSceneViewController()
          let searchResultCoordinator = SearchResultsSceneCoordinator(self.rootVC)     
          searchResultVC.searchResultCoordinator = searchResultCoordinator 
          self.rootVC.pushViewController(searchResultVC, animated: true) 
   }
}

以同樣的方式完成SearchResultSceneCoordinator. 從上面的的代碼中可以看出鲸鹦,我們跳轉(zhuǎn)邏輯中只做了兩件事:初始化ViewController和裝配該ViewController對(duì)應(yīng)的Coordinator叛溢。這步完成之后罐柳,所有UI之間就已經(jīng)按照業(yè)務(wù)邏輯串聯(lián)起來(lái)了。下一步就是根據(jù)業(yè)務(wù)邏輯满力,讓用App State在UI之間流轉(zhuǎn)起來(lái)焕参。
第三步:引入ReSwift架構(gòu)構(gòu)建Redux風(fēng)格的應(yīng)用狀態(tài)管理機(jī)制
首先轻纪,跟著ReSwift官方指導(dǎo)選取你喜歡的方式引入ReSwift框架,筆者使用的是Carthage叠纷。
定義App State
然后刻帚,需要根據(jù)業(yè)務(wù)定義出整個(gè)App的State,定義State的方式可以從業(yè)務(wù)上建模涩嚣,也可以根據(jù)UI需求來(lái)建模崇众,筆者偏向于從UI需求建模,這樣的State更容易和UI進(jìn)行綁定航厚。在本例中主要的State有:

struct AppState: StateType { 
      var property:PropertyState 
      ...
}

struct PropertyState { 
        var searchCriteria:SearchCriteria? 
        var properties:[PropertyDetail]? 
        var selectedProperty:Int = -1
}

struct SearchCriteria { 
        let placeName:String? 
        let centerPoint:String?
}

struct PropertyDetail { 
        var title:String 
        ...
}

定義好State的模型之后顷歌,接著就需要把AppState綁定到Store上,然后直接把Store以全局變量的形式添加到AppDelegate中幔睬。
let mainStore = Store<AppState>( reducer: AppReducer(), state: nil )

把App State綁定到對(duì)應(yīng)的UI上
注入之后眯漩,就可以把AppState中的屬性綁定到對(duì)應(yīng)的UI上了,注意溪窒,接收數(shù)據(jù)綁定應(yīng)該是每個(gè)頁(yè)面的頂層ViewController坤塞,其它的子View都應(yīng)該只是以property的形式接收ViewController傳遞的值。綁定AppState需要做兩件事:訂閱AppState

override func viewWillAppear(_ animated: Bool) { 
    super.viewWillAppear(animated) 
    mainStore.subscribe(self) { state in 
        state 
    } 
} 

override func viewWillDisappear(_ animated: Bool) { 
    super.viewWillDisappear(animated) 
    mainStore.unsubscribe(self) 
}

和實(shí)現(xiàn)StoreSubscriber的newState方法

class SearchSceneViewController: StoreSubscriber { 
    ...... 
    override func newState(state: AppState) { 
        self.update(state: state) 
        super.newState(state: state) 
    }
     ......
} 

經(jīng)過(guò)綁定之后澈蚌,每一次的AppState修改都會(huì)通知到ViewController,ViewController就可以根據(jù)AppState中的內(nèi)容更新自己的UI了灼狰。
定義Actions和Reducers實(shí)現(xiàn)App State更新機(jī)制
綁定好UI和AppState之后宛瞄,接下來(lái)就應(yīng)該實(shí)現(xiàn)改變AppState的機(jī)制了,首先需要定義會(huì)改變AppState的Action們
struct UpdateSearchCriteria: Action { let searchCriteria:SearchCriteria}......

然后交胚,在AppCoordinator中根據(jù)業(yè)務(wù)邏輯把對(duì)應(yīng)的Action分發(fā)出去, 如果有異步請(qǐng)求份汗,還需要使用ActionCreator來(lái)請(qǐng)求數(shù)據(jù),然后再生成Action發(fā)送出去

func searchProperties(searchCriteria: SearchCriteria, _ callback:(() -> Void)?) -> ActionCreator { 
    return { state, store in 
        store.dispatch(UpdateSearchCriteria(searchCriteria: searchCriteria)) 
        self.propertyApi.findProperties( searchCriteria: searchCriteria, success: { (response) in 
            store.dispatch(UpdateProperties(response: response))       
            store.dispatch(EndLoading()) callback?() }, failure: { (error) in 
            store.dispatch(EndLoading()) 
            store.dispatch(SaveErrorMessage(errorMessage: (error?.localizedDescription)!)) 
        } ) 
    return StartLoading() 
    } 
}

Action分發(fā)出去之后蝴簇,初始化Store時(shí)注入的Reducer就會(huì)接收到相應(yīng)的Action杯活,并根據(jù)自己的業(yè)務(wù)邏輯和當(dāng)前App State的狀態(tài)生成一個(gè)新的App State

func propertyReducer(_ state: PropertyState?, action: Action) -> 
    PropertyState { 
        var state = state ?? PropertyState() 
        switch action { 
            case let action as UpdateSearchCriteria: 
                state.searchCriteria = action.searchCriteria 
            ... 
            default: break 
    } 
    return state 
}

最終Store以Reducer生成的新App State替換掉老的App State完成了應(yīng)用狀態(tài)的更新。
以上三步就是一個(gè)完整的架構(gòu)實(shí)踐步驟熬词,該示例的所有源代碼可以在筆者的Github上找到旁钧。
總結(jié)
以解決掉Massive ViewController的iOS應(yīng)用架構(gòu)之爭(zhēng)持續(xù)多年,筆者也參與了公司內(nèi)外的多場(chǎng)討論互拾,架構(gòu)本無(wú)好壞歪今,只是各自適應(yīng)不同的上下文而已。本文中提到的架構(gòu)方式使用了多種模式颜矿,它們各自解決了架構(gòu)上的一些問(wèn)題寄猩,但并不是一定要捆綁在一起使用,大家完全可以根據(jù)需要裁剪出自己需要的模式骑疆,希望本文中提到的架構(gòu)模式能夠給你帶來(lái)一些啟迪田篇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末替废,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泊柬,更是在濱河造成了極大的恐慌舶担,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彬呻,死亡現(xiàn)場(chǎng)離奇詭異衣陶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)闸氮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門剪况,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蒲跨,你說(shuō)我怎么就攤上這事译断。” “怎么了或悲?”我有些...
    開(kāi)封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵孙咪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我巡语,道長(zhǎng)翎蹈,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任男公,我火速辦了婚禮荤堪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枢赔。我一直安慰自己澄阳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布踏拜。 她就那樣靜靜地躺著碎赢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪速梗。 梳的紋絲不亂的頭發(fā)上肮塞,一...
    開(kāi)封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音镀琉,去河邊找鬼峦嗤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛屋摔,可吹牛的內(nèi)容都是我干的烁设。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼装黑!你這毒婦竟也來(lái)了副瀑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恋谭,失蹤者是張志新(化名)和其女友劉穎糠睡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疚颊,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈孔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了材义。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片均抽。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖其掂,靈堂內(nèi)的尸體忽然破棺而出油挥,到底是詐尸還是另有隱情,我是刑警寧澤款熬,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布深寥,位于F島的核電站,受9級(jí)特大地震影響贤牛,放射性物質(zhì)發(fā)生泄漏惋鹅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一盔夜、第九天 我趴在偏房一處隱蔽的房頂上張望负饲。 院中可真熱鬧,春花似錦喂链、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盲链,卻和暖如春蝇率,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刽沾。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工本慕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侧漓。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓锅尘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親布蔗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子藤违,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評(píng)論 25 707
  • 使用ReSwift管理應(yīng)用狀態(tài) 前面提到引入App Coordinator之后浪腐,ViewController的剩下...
    張霸天閱讀 1,965評(píng)論 0 3
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)顿乒,斷路器议街,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 前言 隨著用戶的需求越來(lái)越多,對(duì)App的用戶體驗(yàn)也變的要求越來(lái)越高璧榄。為了更好的應(yīng)對(duì)各種需求特漩,開(kāi)發(fā)人員從軟件工程的角...
    一縷殤流化隱半邊冰霜閱讀 87,212評(píng)論 214 1,098
  • 很盼望過(guò)年,過(guò)年爸就會(huì)回來(lái)了骨杂,一家人在一起心總是踏實(shí)的涂身。 昨天把幾乎整個(gè)院子的雪都清了,媽晚上給我做的肉說(shuō)是為了犒...
    啤酒泡泡123閱讀 139評(píng)論 0 0