【RxSwift 實踐系列 1/3】為什麼使用RxSwift

lizyyy發表於2018-03-05
  • 從mvvm架構開始講起
  • 舉個栗子:RxSwift 能做什麼

從mvvm架構開始講起

MVC是目前主流的客戶端程式設計框架。在iOS開發中,系統為我們實現好了公共的檢視類:UIView 和控制器類:UIViewController。

開發過程中,你一定在Controller中寫過為View格式化資料的程式碼,為什麼我們就這麼自然的把格式化資料的程式碼放到了Controller,一個很直接的答案,就是M和V都不適合。 格式化資料的程式碼肯定不適合放在Model裡,而View只應該負責為使用者顯示內容,它完全不應該關心自己具體顯示的是什麼? 於是,就只剩下Controller了,索性就塞給它吧,於是隨著我們的UI越發複雜,Controller就越臃腫,也不容易做測試,更別說複用了。

MVC這種分層方式雖然清楚,但是如果使用不當,大量程式碼都集中在Controller之中,viewControllers有很大概率充斥著各種既不適合放在model也不適合放在view裡的程式碼。專案過大後,Controller的優化從來沒有停止過,總結了一些方案:

1. 將 UITableView 的 Data Source 分離到另外一個類中。

2. 將資料獲取和轉換的邏輯分別到另外一個類中。

3. 將拼裝控制元件的邏輯,分離到另外一個類中。

總結來就是Controller裡只放不能複用的程式碼
複製程式碼

相對於 MVC 的歷史來說,MVVM 是一個相當新的架構,MVVM 最早於2005年被微軟的WPF和 Silverlight 的架構師 John Gossman 提出,並且應用在微軟的軟體開發中。當時 MVC 已經被提出了 20 多年了,可見兩者出現的年代差別有多大。

MVC:

Model <-> Controller <-> View

MVVM:

Model <-> ViewModel <-> Controller <-> View
複製程式碼

可以看到,這個View Model就是MVVM新引入的東西。一方面,它替代Model為Controller提供了所有的資料介面;另一方面,他也替代了Controller向Model寫回資料。這樣Controller就可以只專注於從資料到檢視的過渡。在後面的視訊中我們就會看到,這樣做可以有效的改善Controller的體積以及可測試性。

1、View不應瞭解任何Controller的細節,它只是一個用於展示內容的白板,給它什麼,它就顯示什麼。無論是MVC,還是MVVM,這都是一定要遵循的原則;

2、Controller不應該瞭解任何Model的細節。

3、View Model擁有 Model 。在原來的MVC模式中,Model 是被 Controller 擁有的,但是在 MVVM 中,Model被 View Model 持有。

4、Model不應該瞭解擁有它的View Model。

MVVM 在使用當中,通常還會利用雙向繫結技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。所以,MVVM模式有些時候又被稱作:model-view-binder 模式。在 iOS 中,可以使用 KVO 或 Notification 技術達到這種效果,因為KVO的程式碼複雜,衍生出了ReactiveCocoa,Rxswift 的工具.他們就是響應式程式設計。

在講之前,我們需要了解以下概念:函數語言程式設計(Functional Programming)和響應式程式設計(React Programming)它們的結合可以很方便地實現資料的繫結。

函數語言程式設計(Functional Programming),函式也變成一等公民了,可以擁有和物件同樣的功能,例如當成引數傳遞,當作返回值等。

響應式程式設計(React Programming),原來我們基於事件(Event)的處理方式都弱了,現在是基於輸入(在 ReactiveCocoa 裡叫 Signal)的處理方式。輸入還可以通過函數語言程式設計進行各種 Combine 或 Filter,盡顯各種靈活的處理。

無狀態(Stateless),狀態是函式的魔鬼,無狀態使得函式能更好地測試。

不可修改(Immutable),資料都是不可修改的,使得軟體邏輯簡單,也可以更好地測試。

RxSwift 核心概念就是一個觀察者( Observer )訂閱一個可觀察序列( Observable )。觀察者對 Observable 發射的資料或資料序列作出響應。現實世界也是如此:你等待老闆的安排對老闆發出指令做出響應、你坐在家裡等著媽媽發出吃飯的指令,你去吃飯。

舉個例子:RxSwift能做什麼

學習 RxSwift 前,先看從幾個簡單的例子看看RxSwift能做什麼

    Observable.combineLatest(firstName.rx.text, lastName.rx.text) { "\($0!) \($1!)" }
        .map { "Greetings, \($0)" }
        .bind(to: greetingLabel.rx.text)
        .disposed(by: rx.disposeBag)
複製程式碼

這段是官方的例子,他做的事情是:

1. 將 firstName 和 lastName 的 text 值用空格合併起來作為結果傳遞給下一步使用
2. 使用 map 的方法,將上一步得到值前面加上一個 Greeting ,並將該值傳遞給後面使用
3. bindTo 就是繫結,將上一步的值繫結到 greetingLabel 的 text
4. disposed最後做一次資源回收
複製程式碼

最終的效果:當使用者在 firstName 和 lastName 的 textfile 上輸入任何字元,greetingLabel上就會響應,顯示最新的輸入 Greeting+firstName+""+lastName

  • greetingLabel是一個觀察者( Observer )訂閱一個可觀察序列( Observable )firstName 和 lastName,greetingLabel對firstName 和 lastName接收到的使用者輸入做出相應

如果換做傳統的方式實現對UITextField的監聽需要怎樣實現呢:

1. 代理方式

//需要繼承UITextFieldDelegate
override func viewDidLoad() {
    super.viewDidLoad()
    firstName.delegate = self
    lastName.delegate = self
}
    
var firstNameString = ""
var lastNameString = ""
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
    if(textField == firstName){
        firstNameString = textField.text!
    }
    if(textField == lastName){
        lastNameString = textField.text!
    }
    greetingLabel.text = "Greetings,  \(firstNameString) \(lastNameString)"
    return true
}
複製程式碼

2、KVO方式

override func viewDidLoad() {
    super.viewDidLoad()
    firstName.addObserver(self, forKeyPath: "text", options: .new, context: nil)
    lastName.addObserver(self, forKeyPath: "text", options: .new , context: nil)
    view.addSubview(firstName)
    view.addSubview(lastName)
    view.addSubview(greetingLabel)
}
    
//非實時的變化
var firstNameString = ""
var lastNameString = ""
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (object as! UITextField == firstName) {
        firstNameString = firstName.text!
    }
    if (object as! UITextField == lastName) {
        lastNameString = lastName.text!
    }
    greetingLabel.text = "Greetings,  \(firstNameString) \(lastNameString)"
}
複製程式碼

3、通知方式

override func viewDidLoad() {
    super.viewDidLoad()
    firstName.addTarget(self, action: #selector(fieldChange), for: .editingChanged)
    lastName.addTarget(self, action: #selector(fieldChange), for: .editingChanged)
    view.addSubview(firstName)
    view.addSubview(lastName)
    view.addSubview(greetingLabel)
}
    
var firstNameString = ""
var lastNameString = ""
@objc func fieldChange(textField: UITextField){
    if(textField == firstName){
        firstNameString = textField.text!
    }
    if(textField == lastName){
        lastNameString = textField.text!
    }
    greetingLabel.text = "Greetings,  \(firstNameString) \(lastNameString)"
}
複製程式碼

4、直接新增監視

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(fieldChange), name: .UITextFieldTextDidChange, object: firstName)
    NotificationCenter.default.addObserver(self, selector: #selector(fieldChange), name: .UITextFieldTextDidChange, object: lastName)
    view.addSubview(firstName)
    view.addSubview(lastName)
    view.addSubview(greetingLabel)
}
    
var firstNameString = ""
var lastNameString = ""
@objc func fieldChange(notify:NSNotification){
    let textfield = notify.object as! UITextField
    if (textfield == firstName){
        firstNameString = textfield.text!
    }
    if (textfield == lastName){
        lastNameString = textfield.text!
    }
    greetingLabel.text = "Greetings,  \(firstNameString) \(lastNameString)"
}
複製程式碼

相信你已經看出RxSwift的思想和他簡潔程式碼的魅力了吧,別急,接下來我們來學習怎麼從RxSwift官方文件來學習它,之後我們將實踐做一個app,一邊做一邊學習其中的知識點。

【RxSwift 實踐系列 2/3】thinking in Rx- Create和Drive

相關文章