李洪強iOS開發之基於完全解耦合的實驗性iOS架構

基於完全解耦合的實驗性iOS架構

 

 

這周我決定作一個關於完全解耦合的應用架構的實驗。我想探究的主題是:ios

 

「若是全部的應用內通信都經過一個事件流來完成會怎麼樣?」git

 

我構造了一個待辦事項應用,由於這是我一時激動下所能想到的最原始微型的項目。我會大概地說一下應用結構背後的想法,展現具體實現中的一些代碼片斷,而後給出幾個有關利弊的結論。github

 

整個項目在Github上。做爲參考,這篇文章基於0.1標籤下的代碼。數據庫

應用演示swift

 

架構總述安全

 

爲了有一個名字來關聯,我把這個架構叫作EventMVVM。它使用少許的MVVM(Model-View-ViewModel)架構。雖然它使用ReactiveCocoa做爲事件流的管道,可是我在後面會說到許多工具也能夠代替。它是用Swift寫的,這有點重要,因爲Swift的枚舉關聯值的特性以及容易定義和使用值類型。服務器

 

我可以解釋架構的最好方法是命名和列舉參與者,定義它們,再列出規則。架構

 

  • Event併發

  • EventsSignal & EventsObserverapp

  • Server

  • Model

  • ViewModel

  • View

 

Event

 

一個Event是一個消息的構建代碼塊。定義爲枚舉,每種狀況下都有多達一個相關聯的值(注意:這與ReactiveCocoa的Event不一樣)。你能夠把它看做一個強類型NSNotification。每一種狀況約定以Request或Response開始。下面是幾個例子。

 

/// Event.swift
enum Event {
// Model
case RequestReadTodos
case ResponseTodos(Result)
case RequestWriteTodo(Todo)
 
// ...
 
// ViewModel
case RequestTodoViewModels
case ResponseTodoViewModels(Result)
case RequestDeleteTodoViewModel(TodoViewModel)
 
// ...
}
  •  

  • Model和ViewModel"類型"的事件都包括在Event枚舉中(註解:1)。

  • RequestReadTodos沒有參數,由於這個應用不須要預先篩選或排序(註解:2)。

  • 咱們使用Result來封裝返回值或異常(注:3)。

  • 全部枚舉項的關聯值都是值類型,這對於確保系統的健全是很重要的。同一個Event可能被任何一個的線程上的許多對象接收到。

 

EventsSignal & EventsObserver

 

eventsSignal和eventsObserver將是咱們共享的事件流。咱們將把它們注入進類裏,這些類將可以附加觀察者塊到eventsSignal,併發送新的Event到eventsObserver。

 

/// AppContext.swift
class AppContext {
let (eventsSignal, eventsObserver) = Signal.pipe()
 
// ...
}

 

咱們把這個元組放在一個叫作AppContext的類裏。它們使用一個ReactiveCocoa的Signal和一對經過.pipe()建立的觀察者來實現。這裏有一些實現細節,稍後咱們將討論。

 

簡而言之語法以下:

 

// 在流中建立新的觀察者
eventsSignal.observeNext { event in print(event) }
 
// 在流中發送一個新的事件
eventsObserver.sendNext(Event.RequestTodoViewModels)

 

Server

 

Server是一個長久存活的類,它包含觀察者並能發送消息。在咱們的示例應用中,有兩個Server--ViewModelServerhe和ModelServer。這些都是由AppDelegate建立並持有的。從名字你可能會認爲ViewModelServer設置了咱們應用的ViewModel相關的職責的觀察者。例如,它負責爲ViewModels接收請求並知足它們,不是改變事件裏的ViewModel,就是發送一個新的事件請求它須要的數據(註解:4註解:5)。

 

Server表明咱們應用裏的"智能"對象。它們是協調器。它們建立和操縱咱們的ViewModel、Model值類型,並與其餘server經過建立Event和附加在它們之上的值進行交流。

 

Model

 

一個Model是一個包含基本數據的值類型。在標準MVVM裏,它不該該包含任何一個針對底層數據庫的東西。

 

在示例應用中,我用擴展來把Todo model對象序列化成TodoObject用於咱們的Realm數據庫。

 

模型層只知道本身。它不知道ViewModel和View。

 

ViewModel

 

一個ViewModel是一個值類型,它包含在View層裏而且是一個能夠直接使用的屬性。例如,UILabel顯示的文本就該是一個String。ViewModel在init函數裏接收和存儲一個Model對象,並將之轉變爲View層可以使用的。一個ViewModel可以使其餘ViewModels可以被子視圖等使用。

 

按這種解釋(註解:6),ViewModels是徹底惰性的,而且不能異步操做和向事件流發送消息。這確保它們能夠安全地在線程間傳遞。

 

ViewModel不知道View層。它們能夠操做其餘ViewModel和Model。

 

View

 

咱們的View層是UIKit,包括UIViewControllers和UIViews及其子類。雖然個人初衷是探索讓View層也經過事件流發送本身的事件,可是在這個簡單的實現裏倒是沒必要要的,而且多是最令人分心的(註解:7)。

 

View層只容許與View和ViewModel層進行交互。這意味着它對Model一無所知。

 

實現

 

如今咱們對全部的組件系統已經有了一個基本的瞭解,讓咱們深刻進代碼,看看它是如何工做的。

 

The Spec(軟件規格說明書)

 

咱們的待辦列表的特色是什麼?這相似於咱們的Event。(對我來講,這是最激動人心的部分。)Event.swift:

 

  • RequestTodoViewModels:咱們但願可以看到全部待辦事項按預設順序排序,並過濾掉已刪除的條目。

  • RequestToggleCompleteTodoViewModel:咱們須要可以在列表視圖把待辦事項標記爲完成。

  • RequestDeleteTodoViewModel:咱們也須要可以將在列表視圖刪除它們。

  • RequestNewTodoDetailViewModel:咱們須要可以建立新的待辦事項。

  • RequestTodoDetailViewModel:咱們須要可以漂亮地查看/編輯一個待辦事項。

  • RequestUpdateDetailViewModel:咱們須要可以提交咱們的更改。

 

這些都是咱們的請求。它們將全部來自View層。由於這些只是咱們廣播的事件/消息,不必定有直接的一對一的響應。這對咱們同時有積極和消極的後果。

 

影響之一是咱們須要更少類的響應事件。ResponseTodoViewModels和RequestTodoViewModels會有一對一的響應,但RequestToggleCompleteTodoViewModel、RequestDeleteTodoViewModel和RequestUpdateDetailViewModel都會由ResponseTodoViewModel響應。這簡化了咱們的view的代碼,也保證了一個view能夠得到更新並傳給被一個不一樣的view改變的ViewModel,咱們也不須要額外作什麼。

 

RequestNewTodoDetailViewModel和RequestTodoDetailViewModel(又名新建和編輯)將由ResponseTodoDetailViewModel響應。

 

有趣的是,RequestUpdateDetailViewModel必須由ResponseUpdateDetailViewModel和ResponseTodoViewModel響應,由於它們的底層待辦Model改變了。稍後咱們將詳細探討這個場景。

 

爲了知足這些來自View層的請求,ViewModelServer須要有本身的對Model數據的請求。這些都是一對一的請求-響應。

 

  • RequestReadTodos -> ResponseTodos

  • RequestWriteTodo -> ResponseTodo

 

咱們在待辦Model裏經過設置一個flag來實現刪除。這種技術明顯使它能更容易地協調咱們的應用層之間變化。

 

如下是一個很長的圖,有關這四個主要對象如何發送和觀察事件。

系統設置

 

/// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
 
var appContext: AppContext!
var modelServer: ModelServer!
var viewModelServer: ViewModelServer!
 
// ...
 
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 
self.appContext = AppContext()
self.modelServer = ModelServer(configuration: Realm.Configuration.defaultConfiguration, appContext: appContext)
self.viewModelServer = ViewModelServer(appContext: appContext)
 
let todoListViewModel = TodoListViewModel()
let todoListViewController = TodoListViewController(viewModel: todoListViewModel, appContext: appContext)
let navigationController = UINavigationController(rootViewController: todoListViewController)
 
// ...
}
}

 

正如以前所說,AppContext包含元組eventSignal和eventObserver。咱們會將它注入到咱們全部的其餘高層組件,並容許它們進行交流。

 

咱們必須保留ModelServer和ViewModelServer,由於他們沒有view層和互相的直接引用(註解:8)。

 

記得TodoListViewModel只是一個惰性結構。雖然對於這個簡單的應用,咱們可讓TodoListViewController建立本身的ViewModel,可是注入是更好的實踐途徑。你能夠很容易地想象把"列表的列表"功能添加到應用。在這種狀況下咱們(可能?)不須要改變咱們的任何接口。

 

View層:列表

 

實際上咱們的系統邊界很清楚。View層將處理全部ViewModel的請求並觀察全部ViewModel的響應。

 

咱們這個部分的主題是TodoListViewController。做爲參考:

 

// TodoListViewController.swift
final class TodoListViewController: UITableViewController {
let appContext: AppContext
var viewModel: TodoListViewModel
 
// ...
}

 

咱們會發送咱們的第一個事件去請求TodoViewModels來填視圖出現時的列表。

 

// TodoListViewController.swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
 
appContext.eventsObserver.sendNext(Event.RequestTodoViewModels)
}

 

接着咱們須要設置一個觀察者來響應事件。View層的觀察者們老是會放置在viewDidLoad裏,同時它的生命週期和UIViewController自己的同樣。

 

override func viewDidLoad() {
// ...
 
appContext.eventsSignal
// ...
.observeNext { _ in
// ...
}
}

 

剖析一個觀察者

 

如今咱們須要深刻了解語法。咱們全部觀察者的結構很是類似:

 

  • 生命週期

  • 過濾

  • 拆箱

  • 映射

  • 錯誤處理

  • 輸出

 

對於View層,輸出的形式一般是反作用(如更新ViewModel和刷新列表)。對於其餘Server,輸出一般是發送另外一個Event。

 

讓咱們看看Event.ResponseTodoViewModels。

 

appContext.eventsSignal
.takeUntilNil { [weak self] in self } // #1
.map { event -> Result? in // #2
if case let .ResponseTodoViewModels(result) = event {
return result
}
return nil
}
.ignoreNil() // #2
.promoteErrors(NSError) // #3
.attemptMap { $0 } // #3
.observeOn(UIScheduler()) // #4
.flatMapError { [unowned self] error -> SignalProducer in // #3
self.presentError(error)
return .empty
}
.observeNext { [unowned self] todoViewModels in // #5
let change = self.viewModel.incorporateTodoViewModels(todoViewModels)
switch change {
case .Reload:
self.tableView.reloadData()
case .NoOp:
break
}
}
  •  

  • #1:這是一個ReactiveCocoa實現細節,它(至關於註解9)把觀察者生命週期限制在self的生命週期裏。換句話說,當TodoListViewController消失時,中止處理這個觀察者。

  • #2:這裏是咱們在必要時從事件中過濾和拆包的地方。記住,咱們在觀察整個應用發送的Event的消防帶。咱們只想要Event.ResponseTodoViewModels,而且若是獲得,咱們但願它的值被傳遞。對於其餘全部到達的事件,它們會被映射到nil而後被ignoreNil()運算符丟棄。

  • #3:這是咱們的錯誤處理。promoteErrors是一個ReactiveCocoa的實現細節,它將一個沒法報錯的信號轉化成一個能發送錯誤到指定類型的信號。而後attemptMap從Result對象中拆包,並容許咱們使用ReactiveCocoa內建的錯誤處理。flatMapError就是咱們錯誤的反作用,在這種狀況下,錯誤以警報形式呈現。相反,若是咱們用observeError,咱們的觀察者將在第一個錯誤事件後被處理掉,這不是咱們想要的(註解10)。

  • #4:Event能夠被eventsSignal交付到任何線程。所以,對於任何線程的關鍵工做咱們須要指定目標調度器。在這種狀況下,咱們的關鍵工做是UI相關,所以咱們使用UIScheduler。注意,只有在observeOn以後的操做可以在UIScheduler上執行(註解11)。

  • #5:最後,咱們有一個來自正確的事件的非錯值。咱們將使用這個徹底取代TodoListViewModel而且有條件地刷新列表,若是列表有任何真正的改變。

 

記住,這個例子其實是複雜應用的一種,由於有錯誤處理和多個未展開的階段。

 

更多操做

 

咱們將使用UITableViewRowActionde API來發送事件爲待辦事項標誌完成或刪除它們。

 

// TodoListViewController.swift
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let todoViewModel = viewModel.viewModelAtIndexPath(indexPath)
 
let toggleCompleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Normal, title: todoViewModel.completeActionTitle) { [unowned self] (action, path) -> Void in
self.appContext.eventsObserver.sendNext(Event.RequestToggleCompleteTodoViewModel(todoViewModel))
}
 
// ...
 
return [deleteAction, toggleCompleteAction]
}

 

這些Event只是修改ViewModel。View層只關心TodoViewModel粒度級別的變化。

 

咱們想要觀察ResponseTodoViewModel,這使咱們的視圖老是顯示最準確的待辦事項。咱們也想有動畫效果,由於那樣好看。

 

// TodoListViewController.swift - viewDidLoad()
appContext.eventsSignal
// Event.ResponseTodoViewModel
// ...
.observeNext { [unowned self] todoViewModel in
let change = self.viewModel.incorporateTodoViewModel(todoViewModel)
switch change {
case let .Insert(indexPath):
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Top)
case let .Delete(indexPath):
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
case let .Reload(indexPath):
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
case .NoOp:
break
}
}

 

這些都是基本的View層。讓咱們再看看ViewModelServer,看看咱們如何響應這些請求Event和發出新的Event。

 

ViewModel:列表

 

ViewModelServer是一個大的配置觀察者的init函數。

 

// ViewModelServer.swift
final class ViewModelServer {
init(appContext: AppContext) {
// ... all observers go here
}
}

 

Event.RequestTodoViewModels

 

ViewModelServer監聽ViewModel的請求併發送ViewModel響應Event。

 

.RequestTodoViewModels至關簡單。它只是從model層建立一個相對應的請求(註解12)。

 

appContext.eventsSignal
// ... Event.RequestTodoViewModels
.map { _ in Event.RequestReadTodos }
.observeOn(appContext.scheduler)
.observe(appContext.eventsObserver)

 

咱們把這個事件發回eventsObserver來派遣咱們的新Event。注意咱們必須派遣這個事件在一個特定的調度器裏,否側會死鎖。有關ReactiveCocoa的實現細節超出了本文的範圍,因此暫時咱們只要注意必須添加任何觀察者到新事件的映射。

 

Event.ResponseTodos

 

如今咱們能夠獲得一個咱們剛剛發出的Model事件的響應。

 

appContext.eventsSignal
// ... Event.ResponseTodos
.map { result -> Result in
return result
.map { todos in todos.map { (todo: Todo) -> TodoViewModel inTodoViewModel(todo: todo) } }
.mapError { $0 } // placeholder for error mapping
}
.map { Event.ResponseTodoViewModels($0) }
.observeOn(appContext.scheduler)
.observe(appContext.eventsObserver)

 

咱們把Result<[todo], nserror="">映射到Result<[todoviewmodel], nserror="">,並返回result做爲一個新的Event。有一個佔位符,在咱們能夠將Model層的錯誤映射到一個更適合展現給用戶的地方(註解13)。

 

其餘ViewModel事件

 

在view層,咱們看到兩個事件,RequestToggleCompleteTodoViewModel和RequestDeleteTodoViewModel,可能被髮送來動態地改變個別ViewModels。

 

用於刪除的map塊:

 

.map { todoViewModel -> Event in
var todo = todoViewModel.todo
todo.deleted = true
return Event.RequestWriteTodo(todo)
}

 

用於標記已完成的map塊:

 

.map { todoViewModel -> Event in
var todo = todoViewModel.todo
todo.completedAt = todo.complete ? nil : NSDate()
return Event.RequestWriteTodo(todo)
}

 

簡單的轉換,而後咱們發出一個消息。

 

這兩個事件將在Event.ResponseTodo接收響應。

 

.map { result -> Result in
return result.map { todo in TodoViewModel(todo: todo) }
}
.map { Event.ResponseTodoViewModel($0) }

 

其餘要點

 

我不會深究其餘事件。我只會提一些其餘有趣的要點。

 

TodoDetailViewModel

 

TodoDetailViewController接受一個TodoDetailViewModel來容許用戶去改變其屬性。當完成按鈕被點擊,TodoDetailViewController將用它本身的TodoDetailViewModel發送一個請求到ViewModelServer。ViewModelServer會驗證全部的新參數而後回覆一個響應。響應事件Event.ResponseUpdateDetailViewModel頗有趣,由於它將由三個不一樣對象的觀察。

 

  • TodoDetailViewController將觀察它的錯誤。若是有錯誤的驗證,它將在當前上下文前展示錯誤。

  • TodoListViewController將觀察非錯值,做爲一個用戶結束編輯ViewModel的信號去解釋它,而後它應該彈回TodoDetailViewController。

  • ViewModelServer將觀察其自己將發送的消息,由於如今它必須當即建立一個更新待辦事項Model併發送一個寫待辦事項的Event。它的響應會經過正常的Event流傳回並由TodoListViewController透明地更新。

 

ResponseUpdateDetailViewModel

 

我有點想把通常化的CRUD如何進行新建和編輯操做集於一個接口。之前保存過的和未保存的待辦事項均可以一樣處理。驗證被看做是異步的,所以這能夠很容易地被看成一個在服務器端的操做。

 

加載

 

我沒有實現任何加載指示器,只因這是小事。ViewController會觀察它本身的Request事件並打開加載指示器做爲一個反作用。而後它將關閉加載指示器看成Response事件的反作用。

 

惟一標識符

 

有一件事你可能會注意到,在代碼庫中每個值類型必須equatable。因爲請求和響應不直接配對,有一個唯一標識符是可以過濾和操做響應的關鍵。實際上在起做用的有兩個相等的概念。首先是通常的相等,好比"這兩個model有全部參數的值都相同嗎?"。第二個是身份的相等,好比"這兩個model表示的是相同的底層資源嗎?"(即lhs.id == rhs.id)。身份的相等在操做一個已經被更新而且你想替換它的model時,是有用的。

 

測試

 

我認爲測試明顯是在ViewModelServer和ModelServer層。這些Servers註冊的觀察者在本質上是純函數,它們收到一個單獨的事件並派遣一個單獨的事件。一個單元測試示例:

 

// todostreamTests.swift
// ...
 
func testRequestToggleCompleteTodoViewModel() {
viewModelServer = ViewModelServer(appContext: appContext)
let todo = Todo()
XCTAssert(todo.complete == false)
let todoViewModel = TodoViewModel(todo: todo)
let event = Event.RequestToggleCompleteTodoViewModel(todoViewModel)
 
let expectation = expectationWithDescription("")
appContext.eventsSignal.observeNext { (e: todostream.Event) in
if case let todostream.Event.RequestWriteTodo(model) = e {
XCTAssert(model.complete == true)
expectation.fulfill()
}
}
 
appContext.eventsObserver.sendNext(event)
 
waitForExpectationsWithTimeout(1.0, handler: nil)
}

 

上面的部分測試了一個ViewModelServer裏的觀察者,並在ViewModelServer和ModelServer之間的邊界等待得到結果Event。

 

集成測試也不是不可能。如下是一個相同事件的集成測試版本,它再也不等待在View和ViewModelServer層之間的邊界:

 

// todostreamTests.swift
// ...
 
func testIntegrationRequestToggleCompleteTodoViewModel() {
viewModelServer = ViewModelServer(appContext: appContext)
modelServer = ModelServer(configuration: Realm.Configuration.defaultConfiguration, appContext: appContext)
let todo = Todo()
XCTAssert(todo.complete == false)
let todoViewModel = TodoViewModel(todo: todo)
let event = Event.RequestToggleCompleteTodoViewModel(todoViewModel)
 
let expectation = expectationWithDescription("")
appContext.eventsSignal.observeNext { (e: todostream.Event) in
if case let todostream.Event.ResponseTodoViewModel(result) = e {
let model = result.value!.todo
XCTAssert(model.id == todo.id)
XCTAssert(model.complete == true)
expectation.fulfill()
}
}
 
appContext.eventsObserver.sendNext(event)
 
waitForExpectationsWithTimeout(1.0, handler: nil)
}

 

在這種狀況下,後臺有兩個其餘事件同時發送,可是咱們只等待最後一個。

 

這兩個server都處在表層,只對EventSignal有依賴性。

 

回顧

 

咱們已經看了一個很是基本的應用的一些實現,如今讓咱們退一步,看看咱們一路上發現的利弊。

 

  • 利 一些在其餘架構很難的事情變容易了!:D

  • 弊 一些在其餘架構很簡單的事情變難了!:(

  • 利 實際上這種代碼風格頗有趣。

  • 弊 可能有目前未知的性能影響,考慮到存在不少觀察者,每一個又接收大量的必須過濾的事件。

  • 利 線程彷佛很安全。

  • 弊 仍然有不少沒有解決的問題。如何處理圖像加載?身份驗證?特定順序的多步操做?列表重排序?更復雜的視圖改變類型?其餘異步API封裝?問題是無止境的。一個半生不熟的待辦事項應用幾乎沒有擴大系統複雜性的範圍。

  • 利 全部代碼(除了UIKit)風格都很類似且很是實用。

  • 弊 全部的事件是全局的(對於系統來講),所以在系統規模和複雜性上增加後,更多的意想不到的後果可能發生。

  • 弊 在觀察者聲明裏有至關數量的一樣格式的陳詞濫調。

  • 利 更容易理清對象的全部者和生命週期。

  • 弊 使用Result來異常處理並不適合。我直覺有另外一個能作得更好的辦法,我須要研究(註解14)。

  • 利 測試能夠說是一個至關無痛的過程。

  • 利 使得"重放"用戶的整個會議過程成爲可能,經過管道傳輸,從eventsSignal的序列化保存的輸出到eventsObserver的一個新的會話。

  • 利 分析會變得很容易,設置做爲一個單獨的Server-type對象,當它們被放到流中能夠監聽Event並轉換而後在必要時POST到服務器。

 

 

我完成了構建這個待辦事項應用後,我意識到ReactiveCocoa不必定是最好的實現EventMVVM的工具。它的不少特性我並沒用到,我有一些怪癖,它旨在被用而我卻不使用它(註解15)。

 

我決定去試試我可不能夠寫我本身的簡單的爲EventMVVM量身定作的庫來實現EventMVVM。我花了一天的事件來與這個類型系統搏鬥,只由於我有一個最早的念頭--我要繼續試着測試。它只有大約100行代碼。不幸的是,它不能自動化全部我想要的東西,觀察的過程仍有缺點。我會找時間寫一些關於這個庫的事。

 

你能夠在Github看到個人進展。

 

總結

 

探索EventMVVM架構頗有趣。我可能會繼續探索它,做爲兼職。我絕對不會建議用它來實現任何重要的東西。

 

若是你有任何關於EventMVVM的想法,請經過Twitter讓我知道。我肯定這種風格已經有個名稱(也許是觀察者模式?)。

 

只要添加這個觀察者到AppDelegate,就能獲取到系統中傳遞的每一個Event的日誌,該有多酷?

 

appContext.eventsSignal.observeNext { print($0) }

 

1.將來EventMVVM的擴展能夠是ModelEvent或ViewModel事件,而且每個有輸入流。這樣,一個View對象只會看到ViewModel流,而ViewModelServers(稍後我會介紹它)會看到ViewModel和Model流。

 

2.一個更復雜的應用,將須要一個ReadTodosRequest結構來封裝一個分類描述符和謂詞。或者更好的是,一個更完全的TodoListViewModel包含全部這些信息。

 

3.事實證實,在響應自己嵌入一個可選的錯誤參數會更好。不然,就沒法知道這個錯誤與哪一個請求相關。咱們暫時不考慮這個問題。

 

4.你固然能夠把ViewModelServer和ModelServer合併到一個Server(或把一切都放在AppDelegate),但MVVM是幫助咱們分離咱們關心的事。

 

5.我最大的一個未解決的問題是若是Server對象相互大量建立該怎麼辦。任何像樣的應用,一個ViewModelServer的一個流裏有成百上千的觀察者是很笨拙的。它們也可能使用了太多的資源。若是咱們把每一個ViewModel類型分離出ViewModelServers,那麼主ViewModelServer怎麼知道如何管理它們的生命週期?

 

6.我大多數其餘使用MVVM的項目,有些ViewModel是類而且挑起大部分關於異步工做和應用內數據流組織的重擔,有些則是惰性的值類型。這背後的緣由是經過分離邏輯來讓ViewControllers有點"遲鈍"。

 

7.這些類型的事件的例子有ViewControllerDidBecomeActive(UIViewController)和ButtonWasTapped(UIButton)。正如你所看到的,這將打破咱們只有經過流發送值類型的假設,而且更須要深思熟慮。當我在工做中把它與其餘框架一塊兒使用時發現,你能夠跳過不少的障礙來避免UIKit指望你去作的方式,雖然一般你想出來另外一種方式會更糟。

 

8.在"經典"MVVM裏,View將擁有ViewModel的,ViewModel將擁有Model/Controller。

 

9.準確來講,觀察者將被觸發轉換到完成狀態,當任何事件被髮送而且自身再也不是活動的。就咱們的目的而言,這不應是一件大事。雖然還有其餘的辦法來解決這個問題,可是它們須要更多語法上的要求。

 

10.回想起來,讓Result一路穿過observeNext並在同一代碼塊內處理成功和錯誤的狀況,可能更清晰。

 

11.Scheduler是ReactiveCocoa原生的。它很巧妙。

 

12.若是你不熟悉MVVM,您可能想知道爲何View層不直接發出Event.RequestReadTodos而是經過ViewModelServer傳送Event.RequestTodoViewModels。一個受歡迎的間接層是讓咱們的View層不知道全部與Model層相關的事務。它引入了對本身和項目中的其它人的可預測性,全部類型的對象和值遵照同一套規則--哪些它們能夠作,哪些對象它們能夠交互。這顯然是通常化的,並且感受它存在項目的早期,但在大型項目我不多發現它會被毫無根據的優化。

 

13.不包括Model層的枚舉類型錯誤是由於懶。咱們設立的轉換管道已經可以很容易讓咱們對於正確的上下文正確地表示。

 

14.提示:這是添加一個error參數到全部model和ViewModel。

 

15.它能夠用NSNotificationCenter實現(並不是我有試過)。也能夠用其餘的Reactive Swift庫。

相關文章
相關標籤/搜索