前面兩篇關於RxSwift的文章都是一些概念,我估計大夥看了一定是迷迷糊糊的,還是不知道RxSwift怎麼使用,那麼這裡俺就帶領大夥一起去做一個Demo,去實戰一下RxSwift,大夥耐心寫完,理解透徹以後,保證大夥能掌握到RxSwift基本核心用法。
掌握了這篇內容,再回頭看下前面兩篇文章,保證你會豁然開朗某些概念,對RxSwift掌握的更加深入。這篇文章沒有對執行緒過分強調,請求都會在當前執行緒完成,計劃會在下篇文章中講解對執行緒的區別。
Demo
Demo地址是這裡 我們們這個Demo選用了最萬能的登入註冊功能,先來看下Demo的一些基本演示,並未包含所有細節,
註冊介面
- 輸入使用者名稱要大於6個字元,不然密碼不能輸入
- 密碼必須大於6個字元,不然重複密碼不能輸入
- 重複密碼輸入必須和密碼一樣,不然註冊按鈕不能點選
- 點選註冊按鈕,提示註冊失敗或者註冊成功
- 註冊成功會寫入本地的plist檔案,然後輸入使用者名稱會檢測使用者名稱是否已經註冊
登入介面
- 點選輸入使用者名稱,如果本地plist檔案中沒有註冊過這個使用者,會提示使用者名稱不存在
- 點選輸入使用者名稱,如果本地plist檔案中有註冊過這個使用者,會提示使用者名稱可用
- 輸入密碼點選登入,如果密碼錯誤提示密碼錯誤
- 輸入密碼點選登入,如果密碼正確則跳入列表介面,然後提示登入成功
列表介面
- 輸入英雄的首字進行篩選。
好了差不多就先搞這麼些功能吧?什麼你居然認為內容很多?放心東西很簡單的,慢慢地參考著寫唄,每天搞定一個介面就中了,技術妥妥的提升!
用到的RxSwift概念
- 註冊介面,我們使用Observale, Variable, Subject,bingTo等
- 登入介面,我們主要去使用Driver
- 列表介面,這個很簡單,TableView打算後面單獨寫一篇,主要是簡單的tableView展現和搜尋
demo是使用的純MVVM模式,因為RxSwift就是為MVVM而生。不懂MVVM的童鞋請看MVVM模式快速入門 ,我預設大家對MVVM有大致的瞭解。
另外demo使用了carthage引入的RxSwift和RxCocoa,當然你也可以使用cocoapods引入這些東西。具體怎麼引入請大家看github介紹吧。
let’s go
首先請大家建立一個新的Swift專案,然後把RxSwift和RxCocoa引入到專案中。為什麼要引入RxCocoa?RxCocoa是對cocoa進行的Rx擴充套件,他已經包含了一個我們需要使用到的observable流,比如button的tap事件,已經幫我們包裝成了一個observable流。一般做iOS開發的要使用到RxSwift都要用到RxCocoa的,這兩個是相輔相成的。所有在所有的ViewController和ViewModel檔案中引入這兩個檔案
1 2 |
import RxCocoa import RxSwift |
登入介面
這個介面我們主要學習使用Obserable和Subject的使用
大家先在storyboard上面建立好這個樣子的介面
建立需要檔案
然後建立對應的RegisterViewController,他看起來應該是下面這樣子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class RegisterViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var passwordLabel: UILabel! @IBOutlet weak var repeatPasswordTextField: UITextField! @IBOutlet weak var repeatPasswordLabel: UILabel! @IBOutlet weak var registerButton: UIButton! @IBOutlet weak var loginButton: UIBarButtonItem! override func viewDidLoad() { super.viewDidLoad() } } |
另外建立一個RegisterViewModel.swift檔案,一個Protocol.swift檔案,一個Service.swift檔案
我們先寫那個比較好呢?我比較習慣先寫Service,我們就先寫Service吧,service檔案主要負責一些網路請求,和一些資料的訪問操作。然後供ViewModel去使用。
首先我們在Service檔案中建立ValidationService類,最好不要繼承NSObject,Swift中推薦儘量使用原生類。我們需要考慮當文字框裡面內容改變的時候,我們需要把傳來的username進行處理,判斷是否符合我們的條件,然後返回處理結果,也就是狀態。我們在protocol.swift檔案中使用一個列舉表示我們處理結果:所以我們在protocol.swift檔案中新增下面內容
1 2 3 4 5 6 |
// 表示我們的一些請求的結果 enum Result { case ok(message: String) case empty case failed(message: String) } |
username的處理
username的處理我加了一個已存在的判斷,效果如下
- 如果你已經註冊過這個使用者,那麼會直接提示使用者名稱已存在
好了回到我們的ValidationService類中,我們寫處理username的方法。他應該看起來是下面這樣子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class ValidationService { static let instance = ValidationService() private init() {} let minCharactersCount = 6 //這裡面我們返回一個Observable物件,因為我們這個請求過程需要被監聽。 func validateUsername(_ username: String) -> Observable { if username.characters.count == 0 {//當字元等於0的時候什麼都不做 return .just(.empty) } if username.characters.count Bool { let filePath = NSHomeDirectory() + "/Documents/users.plist" let userDic = NSDictionary(contentsOfFile: filePath) let usernameArray = userDic!.allKeys as NSArray if usernameArray.contains(username) { return true } else { return false } } } |
下面開始寫我們的RegisterViewModel.swift,我們宣告一個username他是一個Variable的物件,為什麼是一個Variable物件呢?因為username既是一個observable也是一個observer,所以我們宣告為他為一個Variable物件。我們對username進行處理的應該有一個結,結果應該是需要介面去監聽來改變介面,因為處理的結果不需要是一個observer,所以我們把它宣告為一個Observable型別。所以你的RegisterViewModel類應該是這樣子。
1 2 3 4 5 6 7 8 9 10 11 |
class RegisterViewModel { //input: let username = Variable("") //初始值為"" // output: let usernameUsable: Observable init() { } } |
然後我們在寫我們的RegisterViewController.swift檔案,viewDidLoad()看起來應該是下面這個樣子
1 2 3 4 5 6 7 8 9 10 11 |
let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() let viewModel = RegisterViewModel() usernameTextField.rx.text.orEmpty .bindTo(viewModel.username) .addDisposableTo(disposeBag) } |
- 其中
usernameTextField.rx.text.orEmpty
是RxCocoa庫中的東西,他把TextFiled的text變成了一個Observable,後面的orEmpty我們可以Command點進去看下,他會把String?過濾nil幫我們變為String型別。 - 好了,因為我們的username既是一個observable也是一個observer,此時此刻我們把他當成一個Observer繫結到usernameTextFiled上,監聽我們的usernameTextField流。繫結就是監聽,bingTo裡面裡面的就是監聽者也就是Observer
- 因為我們有監聽,就要有監聽資源的回收,所以我們建立一個disposeBag來盛放我們的這些監聽資源。
好了回到我們的RegisterViewModel類中,我們新增下面的程式碼
1 2 3 4 5 6 7 8 9 10 11 |
init() { let service = ValidationService.instance usernameUsable = username.asObservable() .flatMapLatest{ username in return service.validateUsername(username) .observeOn(MainScheduler.instance) .catchErrorJustReturn(.failed(message: "username檢測出錯")) } .shareReplay(1) } |
因為username是Variable型別,既可以當observer也可以當observable,viewModel中我們把它當成observable,然後對裡面的元素進行監聽和處理,這裡面我們使用了flatMap,因為我們需要返回一個新的序列,也就是返回處理結果,因為涉及到資料庫操作或者網路請求(當然是模擬的網路請求),所以這個序列需要我們去監聽,這種情況我們使用flatMap(具體請參考)
後面使用.shareReplay(1)是因為我們要保證無論多少個Observer來監聽我們這個序列,username的處理程式碼我們只執行一次,這一次請求結果供多有的observer去使用。
下面我們在RegisterViewController中處理我們的username請求結果。我們在ViewDidLoad中新增下列程式碼
1 2 3 4 5 6 |
viewModel.usernameUsable .bindTo(usernameLabel.rx.validationResult) .addDisposableTo(disposeBag) viewModel.usernameUsable .bindTo(passwordTextField.rx.inputEnabled) .addDisposableTo(disposeBag) |
- 將ViewModel中username處理結果usernameUsable繫結到usernameLabel顯示文案上,根據不同的結果顯示不同的文案
- 將ViewModel中username處理結果usernameUsable繫結到passwordTextField是否可以輸入,根據不同的結果判斷password是否可以輸入。
上面的validationResult,inputEnabled需要我們自己去定製,這就又用到了RxSwift入坑解讀-那些難以理解的細節文章中的UIBindingObserver了,我們需要建立自己的監聽者。具體大家可以好好參考這篇文章。
所以我們在protocol.swift
檔案中新增下列程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
extension Result { var isValid: Bool { switch self { case .ok: return true default: return false } } } extension Result { var textColor: UIColor { switch self { case .ok: return UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0) case .empty: return UIColor.black case .failed: return UIColor.red } } } extension Reactive where Base: UILabel { var validationResult: UIBindingObserver { return UIBindingObserver(UIElement: base) { label, result in label.textColor = result.textColor label.text = result.description } } } extension Reactive where Base: UITextField { var inputEnabled: UIBindingObserver { return UIBindingObserver(UIElement: base) { textFiled, result in textFiled.isEnabled = result.isValid } } } |
- 我們首先對Result進行了擴充套件,新增了一個isValid屬性,如果他的狀態是ok,這個屬性就返回true,否則就返回false
- 然後對Result新增了一個textColor屬性,如果Result屬性為ok的時候顏色就是綠色,否則即使紅色
- 下面我們自定義了一個Observer,對UIlabel進行了擴充套件,根據result結果,進行他的text和textColor的顯示
- 最後我們對UITextField進行擴充套件,根據result結果,進行他的isEnabled進行設定
好了到了這裡我們就可以執行專案來看下程式的執行情況,試著去輸入username嘗試一下吧。激動了不?
總結一下這個過程:
輸入文字框Observable流->ViewModel中username對文字框進行監聽->然後username呼叫service進行處理得到usernameUsable結果流->提示lable對usernameUsable進行監聽重新整理UI。
其實就是兩個流的過程
UI操作->ViewModel->改變資料
資料改變->ViewModel->UI重新整理
哈哈,這就是響應式程式設計,看起來是不是一路的監聽啊
password的處理
有了上面的理解,對password的處理我們就輕車熟路了。很簡單了,有些概念我就不需要解釋太多了。
我們現在Service中新增對password的處理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func validatePassword(_ password: String) -> Result { if password.characters.count == 0 { return .empty } if password.characters.count Result { if repeatedPasswordword.characters.count == 0 { return .empty } if repeatedPasswordword == password { return .ok(message: "密碼可用") } return .failed(message: "兩次密碼不一樣") } |
- validatePassword處理我們輸入的password
- validateRepeatedPassword處理我們的密碼確認
- 上面的返回結果都是一個Result型別的值,因為我們外面不要對這個處理過程進行監聽,所以不必返回一個新的序列
然後我們在RegisterViewModel.swift檔案中,新增需要的observale
1 2 3 4 5 6 7 |
//input: let password = Variable("") let repeatPassword = Variable("") //output: let passwordUsable: Observable //密碼是否可用 let repeatPasswordUsable: Observable //密碼確定是否正確 |
然後我們在RegisterViewController中,新增passwordTextField繫結
1 2 3 4 5 6 |
passwordTextField.rx.text.orEmpty .bindTo(viewModel.password) .addDisposableTo(disposeBag) repeatPasswordTextField.rx.text.orEmpty .bindTo(viewModel.repeatPassword) .addDisposableTo(disposeBag) |
- 將viewModel的password對passwordTextField進行監聽
- 將viewModel的repeatPassword對repeatPasswordTextField進行監聽
然後再轉移到RegisterViewModel中,處理我們的password的輸入
1 2 3 4 5 6 7 8 9 10 |
passwordUsable = password.asObservable() .map { password in return service.validatePassword(password) } .shareReplay(1) repeatPasswordUsable = Observable.combineLatest(password.asObservable(), repeatPassword.asObservable()) { return service.validateRepeatedPassword($0, repeatedPasswordword: $1) } .shareReplay(1) |
- 這裡使用的是map,因為處理密碼不需要去聯網操作,我們不需要對他進行監聽處理,只需要將流中的每一個元素item轉換為result的值。
- 下面對確定密碼的處理,我們使用了一個combineLatest進行聯合,也就是對兩個流的item進行處理,返回處理結果流。
下面轉移到RegisterViewController中對ViewModel中的output進行處理
1 2 3 4 5 6 7 8 9 10 |
viewModel.passwordUsable .bindTo(passwordLabel.rx.validationResult) .addDisposableTo(disposeBag) viewModel.passwordUsable .bindTo(repeatPasswordTextField.rx.inputEnabled) .addDisposableTo(disposeBag) viewModel.repeatPasswordUsable .bindTo(repeatPasswordLabel.rx.validationResult) .addDisposableTo(disposeBag) |
- passwordLabel對viewModel.passwordUsable進行監聽,顯示不同的文案提示
- repeatPasswordTextField對passwordUsable進行監聽,結果ok可輸入狀態,否則就是不可輸入
- repeatPasswordTextField對repeatPasswordUsable進行監聽,顯示不同的文案提示
呼呼!好了執行下程式看看吧,輸入password和確定密碼感受下吧。
註冊按鈕處理
下面我們來這個介面的最後一個button處理吧,比較簡單,貼貼程式碼解釋解釋,輕輕鬆鬆
首先我們寫service裡面的註冊方法
1 2 3 4 5 6 7 8 9 10 |
func register(_ username: String, password: String) -> Observable { let userDic = [username: password] let filePath = NSHomeDirectory() + "/Documents/users.plist" if (userDic as NSDictionary).write(toFile: filePath, atomically: true) { return .just(.ok(message: "註冊成功")) } return .just(.failed(message: "註冊失敗")) } |
- 我是直接把註冊資訊寫入到本地的plist檔案,寫入成功就返回ok,否則就是failed
然後我們來到RegisterViewModel檔案中,新增需要的input和output
1 2 3 4 5 6 |
//input: let registerTaps = PublishSubject() // output: let registerButtonEnabled: Observable let registerResult: Observable |
- registerTaps我們使用了PublishSubject,因為不需要有初試元素,其實前面的Variable都可以換成PublishSubject。大夥可以試試
- 下面就是註冊按鈕是否可用的輸出,這個其實關係到username和password
- 最後就是註冊結果的output
進入RegisterViewController中,我們新增輸入繫結
1 2 3 |
registerButton.rx.tap .bindTo(viewModel.registerTaps) .addDisposableTo(disposeBag) |
然後回到RegisterViewModel檔案中,對button的點選輸入進行處理,我們新增下面這些程式碼,然後我一點點解釋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
registerButtonEnabled = Observable.combineLatest(usernameUsable, passwordUsable, repeatPasswordUsable) { (username, password, repeatPassword) in username.isValid && password.isValid && repeatPassword.isValid } .distinctUntilChanged() .shareReplay(1) let usernameAndPassword = Observable.combineLatest(username.asObservable(), password.asObservable()) { ($0, $1) } registerResult = registerTaps.asObservable().withLatestFrom(usernameAndPassword) .flatMapLatest { (username, password) in return service.register(username, password: password) .observeOn(MainScheduler.instance) .catchErrorJustReturn(.failed(message: "註冊出錯")) } .shareReplay(1) |
- 首先registerButtonEnabled的處理,把username,password和repeatePassword的處理結果繫結到一起,返回一個總的結果流,是一個bool值的流
- 我們現將username和password進行結合,得到一個元素是他倆組合的元組的流
- 然後對registerTaps事件進行監聽,我們拿到每一個元組進行註冊行為,涉及到耗時資料庫操作,我們需要對這個過程進行監聽,所以我們使用flatMap函式,返回一個新的流,
然後回到RegisterViewController檔案中,我們對ViewModel的output進行處理,你需要新增以下程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
viewModel.registerButtonEnabled .subscribe(onNext: { [unowned self] valid in self.registerButton.isEnabled = valid self.registerButton.alpha = valid ? 1.0 : 0.5 }) .addDisposableTo(disposeBag) viewModel.registerResult .subscribe(onNext: { [unowned self] result in switch result { case let .ok(message): self.showAlert(message: message) case .empty: self.showAlert(message: "") case let .failed(message): self.showAlert(message: message) } }) .addDisposableTo(disposeBag) |
- 對registerButtonEnabled進行監聽,根據不同的item對註冊按鈕進行設定
- 對registerResult進行監聽,顯示不同的彈框資訊
彈框方法
1 2 3 4 5 6 |
func showAlert(message: String) { let action = UIAlertAction(title: "確定", style: .default, handler: nil) let alertViewController = UIAlertController(title: nil, message: message, preferredStyle: .alert) alertViewController.addAction(action) present(alertViewController, animated: true, completion: nil) } |
註冊介面終於OK了,希望大家寫完好好思考一下流程哦
註冊介面
這裡面我們主要學習使用Driver的使用
其實Driver和Observable的使用結構是一樣的只是Driver和Observable有點區別,Driver是RxSwift專門針對UI操作,而Observable是一個通用的東西,他們的區別可以參考我的p上一篇文章中的解析
首先我們在StoryBoard新增登入介面,如下,當點選登入的時候,跳轉到我們的登入介面
我們仍然建立LoginViewController.swift和LoginViewModel.swift檔案。
有了上面註冊功能的一些程式碼工具,我們這邊講解就會比較輕鬆一點了。
首先在service中ValidationService新增下面的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func loginUsernameValid(_ username: String) -> Observable { if username.characters.count == 0 { return .just(.empty) } if usernameValid(username) { return .just(.ok(message: "使用者名稱可用")) } return .just(.failed(message: "使用者名稱不存在")) } func login(_ username: String, password: String) -> Observable { let filePath = NSHomeDirectory() + "/Documents/users.plist" let userDic = NSDictionary(contentsOfFile: filePath) if let userPass = userDic?.object(forKey: username) as? String { if userPass == password { return .just(.ok(message: "登入成功")) } } return .just(.failed(message: "密碼錯誤")) } |
- 判斷使用者名稱是否可用,如果本地plist檔案中有這個使用者名稱,就表示可以使用這個使用者名稱登入,使用者名稱可用
- 登入方法,如果usernmae和password都正確的話,就是登入成功,否則就是密碼錯誤了
然後是LoginViewModel.swift,我們寫成下面這樣子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class LoginViewModel { // output: let usernameUsable: Driver let loginButtonEnabled: Driver let loginResult: Driver init(input: (username: Driver, password: Driver, loginTaps: Driver), service: ValidationService) { usernameUsable = input.username .flatMapLatest { username in return service.loginUsernameValid(username) .asDriver(onErrorJustReturn: .failed(message: "連線server失敗")) } let usernameAndPassword = Driver.combineLatest(input.username, input.password) { ($0, $1) } loginResult = input.loginTaps.withLatestFrom(usernameAndPassword) .flatMapLatest { (username, password) in return service.login(username, password: password) .asDriver(onErrorJustReturn: .failed(message: "連線server失敗")) } loginButtonEnabled = input.password .map { $0.characters.count > 0 } .asDriver() } } |
- 首先我們宣告的output都是Driver型別的,第一個是username處理結果流,第二個是登入按鈕是否可用的流,第三個是登入結果流
- 下面的init方法,看著和剛才的註冊介面不一樣。這種寫法我參考了官方文件的寫法,讓大家知道有這種寫法。但是我並不推薦大家使用這種方式,因為如果Controller中的元素很多的話,一個一個傳過來是很可笑的把。
- 初始化方法傳入的是一個input元組,包括username的Driver序列,password的Driver序列,還有登入按鈕點選的Driver序列,還有Service物件,需要Controller傳遞過來,其實Controller不應該擁有Service物件。
- 初始化方法中,我們對傳入的序列進行處理和轉換成相對應的output序列。
- 大家看到了使用了Driver,我們不再需要shareReplay(1)
- 明白了註冊介面的東西,這些東西也自然很簡單了。
下面我們在LoginViewController.swift中新增下列程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
override func viewDidLoad() { super.viewDidLoad() viewModel = LoginViewModel(input: (username: usernameTextField.rx.text.orEmpty.asDriver(), password: passwordTextField.rx.text.orEmpty.asDriver(), loginTaps: loginButton.rx.tap.asDriver()), service: ValidationService.instance) viewModel.usernameUsable .drive(usernameLabel.rx.validationResult) .addDisposableTo(disposeBag) viewModel.loginButtonEnabled .drive(onNext: { [unowned self] valid in self.loginButton.isEnabled = valid self.loginButton.alpha = valid ? 1 : 0.5 }) .addDisposableTo(disposeBag) viewModel.loginResult .drive(onNext: { [unowned self] result in switch result { case let .ok(message): self.performSegue(withIdentifier: "container", sender: self) self.showAlert(message: message) case .empty: self.showAlert(message: "") case let .failed(message): self.showAlert(message: message) } }) .addDisposableTo(disposeBag) } |
- 我們給viewModel傳入相應的input的Driver序列
- 將viewModel中的output進行相應的監聽,如果是Driver序列,我們這裡不使用bingTo,而是使用的Driver,用法和bingTo一模一樣。
- Deriver的監聽一定發生在主執行緒,所以很適合我們更新UI的操作
- 登入成功會跳轉到我們的列表介面
哈哈,我們已經完成了兩個介面的編寫了,相信你對RxSwift已經有了一個全新的認識了吧。
列表介面
下面是列表介面,限於篇幅的原因,這裡我只寫展現,具體的搜尋功能和其他tableView的展現技術,我會在下篇文章中進行分享。
在StoryBoard檔案中新建列表介面
然後建立相應的ContainerViewController.swift和ContainerViewModel.swift檔案。和Hero.swift檔案。將所需的圖片資源匯入,下載Demo,Demo中有。
首先編寫我們Hero類
1 2 3 4 5 6 7 8 9 10 11 |
class Hero: NSObject { var name: String var desc: String var icon: String init(name: String, desc: String, icon: String) { self.name = name self.desc = desc self.icon = icon } } |
然後我們在Service檔案中新增一個新的Service類,或者在原來的類中新增方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class SearchService { static let shareInstance = SearchService() private init() {} func getHeros() -> Observable { let herosString = Bundle.main.path(forResource: "heros", ofType: "plist") let herosArray = NSArray(contentsOfFile: herosString!) as! Array var heros = [Hero]() for heroDic in herosArray { let hero = Hero(name: heroDic["name"]!, desc: heroDic["intro"]!, icon: heroDic["icon"]!) heros.append(hero) } return Observable.just(heros) .observeOn(MainScheduler.instance) } } |
- 從本地拉去資料,然後轉換成Hero模型
- 我們返回的是一個元素是Hero陣列的Observable流。接下來更新UI的操作要在主執行緒中
然後看下我們的ContainerViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ContainerViewModel { // output: var models: Driver init(withSearchText searchText: Observable, service: SearchService) { models = searchText .debug() .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .flatMap { text in return service.getHeros(withName: text) }.asDriver(onErrorJustReturn: []) } } |
- 我們的output是一個Driver流,因為更新tableView是UI操作
- 然後我們使用service拉去資料的操作應該是在後臺執行緒去執行,所以新增了observeOn操作
- 然後使用flatMap返回新的Observable流,轉換成output的Driver流
我們的ContainerViewController類就很簡單了
1 2 3 4 5 6 7 8 9 10 11 12 |
override func viewDidLoad() { super.viewDidLoad() let viewModel = ContainerViewModel(withSearchText: searchBarText, service: SearchService.shareInstance) viewModel.models .drive(tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (row, element, cell) in cell.textLabel?.text = element.name cell.detailTextLabel?.text = element.desc cell.imageView?.image = UIImage(named: element.icon) } .addDisposableTo(disposeBag) } |
- 這裡我們不需要設定dataSource
- 將資料繫結到tableView的items元素,這是RxCocoa對tableView的一個擴充套件方法。
- 其實MVVM模式中,model層不應該暴露給ViewController,這裡我們這簡單處理了下,不必在意這些細節
我們點進去可以看下,一共有三個items方法,並且上面都有些使用方法,我們使用的這個是
1 |
public func items<s>(cellIdentifier: String, cellType: Cell.Type = default) -> (O) -> (@escaping (Int, S.Iterator.Element, Cell) -> Swift.Void) -> Disposable</s> |
這是一個科裡化的方法,不帶section的時候使用這個,他有兩個引數,一個是迴圈利用的cell的identifier,一個cell的型別。後面會返回的是一個閉包,在閉包裡對cell進行設定。方法用起來比較簡單,就是有點難理解。
好了現在執行你的程式碼吧,騷年開始在RxSwift的海洋中自由泳吧
小夥伴們如果感覺文章可以,可以關注博主部落格
小夥伴們也可以關注博主微博,探索博主內心世界
如要轉載請註明出處。