一、前言
- 江湖上都在說現在就要趕緊學 swift 了,即將是 swift 的天下了。在 api 變化不大的情況下,swift 作為一門新的語言,集眾家之所長,普通編碼確實比 oc 要好用的多了
- 老早就聽說 MVVM 的概念及響應式函數式編程,微軟確實厲害。自己最近沒什么事,就前來入坑了
二、學習方式
- 參考別人寫的一些博客,對於概念先有個理解,然后參考官方 example,就可以開始學習了
- 推薦文章:
http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/
http://www.codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/
三、自己寫注冊登錄及 tableView 的一點理解
-
關於
觀察者和被觀察者(Observable)發出序列- UI 控件在 RxCocoa 下某些屬性都是被觀察者(Observable),都可以發出序列,常見的有
- 控件的 text 類型是
ControlProperty<String>,最終遵循ObservableType協議 - 按鈕的點擊 tap 類型是
ControlEvent<Void>,最終遵循ObservableType協議
- 控件的 text 類型是
- 對於設置 UI 控件的一些 Bool 類型的屬性,如可輸入,可點擊,一般用
UIBindingObserver<UIElementType, Value>(遵循ObserverType協議) 來生成觀察者,對接受的數據條件進行判斷是否可以輸入、可點擊
// MARK: RX 擴展 計算型屬性 // textfield 根據展示驗證后的結果能否輸入,驗證過了才能輸入 extension Reactive where Base: UITextField { var inputEnable: UIBindingObserver<Base, ValidationResult> { return UIBindingObserver(UIElement: base, binding: { (textField, result) in textField.isEnabled = result.isValid }) } }- 關於在 VM 中常用的
Subject- Variable、PublishSubject 是 Subject 的一種,可當觀察者被 bindTo,可當序列數據源 Observable
- Variable 它不會因為錯誤終止也不會正常終止, 適合做數據源,可以用於控件的 text 屬性
- PublishSubject 與普通的Subject不同,在訂閱時並不立即觸發訂閱事件,而是允許我們在任意時刻手動調用onNext(),onError(),onCompleted來觸發事件,可以用於按鈕的點擊
- Variable、PublishSubject 是 Subject 的一種,可當觀察者被 bindTo,可當序列數據源 Observable
- 關於
被觀察者(Observable)的一些常用的 api- map 不會產生新的序列
- flatMapLatest 會產生新的序列
- combineLatest 不會產生新的序列
- UI 控件在 RxCocoa 下某些屬性都是被觀察者(Observable),都可以發出序列,常見的有
-
關於 MVVM 文件夾分類
-
之前 MVC 的與 iOS 里的 Controller、View 一一對應,很好理解,而 MVVM 里 Controller 屬於 V 了,負責處理控制器跳轉和將 View 和 VM 綁定等,大部分的業務邏輯代碼都在 VM 里,感覺應該是這樣

-
自始至終感覺 iOS 里的 model 這一層很輕,有時僅僅是建立了模型類而已。感覺應該是各種數據操作如數據庫查詢等都應該是 model 這一層的
-
-
關於
** 雙向綁定 **- 首先要有一些控件,理清楚需要監聽這些控件的哪些屬性值
- 然后 VM 里建立好這些屬性值對應的 Subject
- 一般控制器里生成 VM 對象,將控件的 Observable 的屬性綁定到 VM 的 Subject 屬性上,這樣可在 VM 里監聽到控件屬性值的改變,此時 Subject 是 Observer,完成一次綁定
- 在 VM 內,將 Subject 變成 Observable,生成對應 VM 可被觀察者屬性(用屬性保存加工變換后的 Observable )。這里 Subject 是 Observable,可通過 map 、filter 等各種操作,操作的數據就是 Subject 觀察到的序列,相應模塊的整個業務邏輯都在此處。
- 在控制器里,再將 VM 的可被觀察者屬性綁定到 UI 控件上,在此完成雙向綁定
override func viewDidLoad() { super.viewDidLoad() let regiestViewModel = RegiestViewModel() // 這里做綁定: UI控件 --> VM VM -> UI控件 // 1.UI控件 --> VM nameTextField.rx.text.orEmpty .bindTo(regiestViewModel.username) .addDisposableTo(disposeBag) pwdTextField.rx.text.orEmpty .bindTo(regiestViewModel.userPwd) .addDisposableTo(disposeBag) repeatPwdTextField.rx.text.orEmpty .bindTo(regiestViewModel.repeatPwd) .addDisposableTo(disposeBag) regiestBtn.rx.tap .bindTo(regiestViewModel.registerTaps) .addDisposableTo(disposeBag) // 2.VM -> UI控件 // 顯示結果的 label 上 regiestViewModel.usernameValid .bindTo(nameTipLabel.rx.validResult) .addDisposableTo(disposeBag) // 綁定 密碼框是否可以輸入 regiestViewModel.usernameValid .bindTo(pwdTextField.rx.inputEnable) .addDisposableTo(disposeBag) regiestViewModel.passwordValid .bindTo(pwdTipLabel.rx.validResult) .addDisposableTo(disposeBag) regiestViewModel.passwordValid .bindTo(repeatPwdTextField.rx.inputEnable) .addDisposableTo(disposeBag) regiestViewModel.repeatPwdValid .bindTo(repeatPwdTipLabel.rx.validResult) .addDisposableTo(disposeBag) // 按鈕不是綁定, 按鈕是 subcribe, 需要操作的 regiestViewModel.registerButtonEnabled .subscribe (onNext: { [weak self] (result) in self?.regiestBtn.isEnabled = result self?.regiestBtn.alpha = result ? 1 : 0.8 }) .addDisposableTo(disposeBag) // 注冊結果 : 注冊成果或失敗 要展示在 UI 上 regiestViewModel.registeResult .subscribe(onNext:{ [weak self] result in switch result { case let .failed(message): self?.showAlter(message: message) case let .ok(message): self?.showAlter(message: message) case .empty: self?.showAlter(message: "") } }) .addDisposableTo(disposeBag) // 跳轉到登錄界面按鈕的點擊 loginVcBtn.rx.tap .subscribe(onNext: { let loginVc = LoginViewController() loginVc.title = "請登錄" self.navigationController?.pushViewController(loginVc, animated: true) }) .addDisposableTo(disposeBag) } -
關於
tableView-
這里需要 RxDataSources 這個配套的框架
-
控制器里需要一個 dataSource
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, HerosItem>>() -
控制器里用這個 dataSource 配置 cell
dataSource.configureCell = { (_, tableView, indexPath, item) in var cell = tableView.dequeueReusableCell(withIdentifier: "herosCell") if cell == nil { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "herosCell") } cell!.imageView?.image = UIImage(named: item.icon) cell!.textLabel?.text = item.name cell!.detailTextLabel?.text = item.intro return cell! } -
用 VM 創建出數據 Observable,發出序列,綁定到dataSource 上,完成數據的綁定
homeViewMode.getSearchResult() .bindTo(tableView.rx.items(dataSource: dataSource)) .addDisposableTo(disposeBag) -
其他操作
- tableView 的代理
tableView.rx .setDelegate(self) .addDisposableTo(disposeBag) - tableView cell 的點擊
tableView.rx.itemSelected .map { [weak self] indexPath in return (indexPath, self?.dataSource[indexPath]) } .subscribe(onNext: {(indexPath, item) in self.showAlter(item: item) }) .addDisposableTo(disposeBag)
- tableView 的代理
-
感覺像一個固定的代碼模式,將數據源的代碼都移到 VM 里了
-
另外如果做實時搜索的話,用雙向綁定效果那是極好的,將搜索框的搜索關鍵字綁定到 VM 里,在用 VM 產生序列綁定到 tableView 上
-
四、本例 demo 地址
五、其他
- MVVM 的使用並沒有那么普及,大多數還是 MVC,關於 MVC 的理解和減少控制器的代碼量和維護難度是一個難題,有篇文章可參考 http://www.infoq.com/cn/articles/rethinking-mvc-mvvm
- 對於 RxSwift,像 swift 里的可選類型的處理及 swift 里的多種閉包類型及簡寫,理解還急需提高;對 RxSwift 的 api 還要繼續熟悉,以及后面的多線程及整個項目都用 MVVM 部署還需要更多的實踐
