iOS RxSwift實戰教程-核心用法

tqka2785 8年前發布 | 30K 次閱讀 RxSwift

這里俺就帶領大伙一起去做一個Demo,去實戰一下RxSwift,大伙耐心寫完,理解透徹以后,保證大伙能掌握到RxSwift基本核心用法。

掌握了這篇內容,再回頭看下前面兩篇文章,保證你會豁然開朗某些概念,對RxSwift掌握的更加深入。 這篇文章沒有對線程過分強調,請求都會在當前線程完成,計劃會在下篇文章中講解對線程的區別 。

Demo

Demo地址是 這里 咱們這個Demo選用了最萬能的登錄注冊功能,先來看下Demo的一些基本演示,并未包含所有細節,

注冊界面

  • 輸入用戶名要大于6個字符,不然密碼不能輸入
  • 密碼必須大于6個字符,不然重復密碼不能輸入
  • 重復密碼輸入必須和密碼一樣,不然注冊按鈕不能點擊
  • 點擊注冊按鈕,提示注冊失敗或者注冊成功
  • 注冊成功會寫入本地的plist文件,然后輸入用戶名會檢測用戶名是否已經注冊

登錄界面

  • 點擊輸入用戶名,如果本地plist文件中沒有注冊過這個用戶,會提示用戶名不存在
  • 點擊輸入用戶名,如果本地plist文件中有注冊過這個用戶,會提示用戶名可用
  • 輸入密碼點擊登錄,如果密碼錯誤提示密碼錯誤
  • 輸入密碼點擊登錄,如果密碼正確則跳入列表界面,然后提示提示登錄登錄成功

列表界面

  • 輸入英雄的首字進行篩選。

好了差不多就先搞這么些功能吧?什么你居然認為內容很多?放心東西很簡單的,慢慢地參考著寫唄,每天搞定一個界面就中了,技術妥妥的提升!?

用到的RxSwift概念

  • 注冊界面,我們使用Observale, Variable, 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文件中引入這兩個文件

import RxCocoa
import RxSwift

登錄界面

這個界面我們主要學習使用Obserable和Subject的使用

大家先在storyboard上面建立好這個樣子的界面

建立需要文件

然后建立對應的RegisterViewController,他看起來應該是下面這樣子

classRegisterViewController: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 funcviewDidLoad() {
        super.viewDidLoad()
    }
}

另外建立一個RegisterViewModel.swift文件,一個Protocol.swift文件,一個Service.swift文件

我們先寫那個比較好呢?我比較習慣先寫Service,我們就先寫Service吧,service文件主要負責一些網路請求,和一些數據的訪問操作。然后供ViewModel去使用。

首先我們在Service文件中創建ValidationService類,最好不要繼承NSObject,Swift中推薦盡量使用原生類。我們需要考慮當文本框里面內容改變的時候,我們需要把傳來的username進行處理,判斷是否符合我們的條件,然后返回處理結果,也就是狀態。我們在protocol.swift文件中使用一個枚舉表示我們處理結果:所以我們在 protocol.swift 文件中添加下面內容

// 表示我們的一些請求的結果
enumResult{
    case ok(message: String)
    case empty
    case failed(message: String)
}

username的處理

username的處理我加了一個已存在的判斷,效果如下

  • 如果你已經注冊過這個用戶,那么會直接提示用戶名已存在

好了回到我們的 ValidationService 類中,我們寫處理username的方法。他應該看起來是下面這樣子。

classValidationService{
    static let instance = ValidationService()

    private init() {}

    let minCharactersCount = 6

    //這里面我們返回一個Observable對象,因為我們這個請求過程需要被監聽。
    funcvalidateUsername(_username: String) -> Observable<Result> {

        if username.characters.count == 0 {//當字符等于0的時候什么都不做
            return .just(.empty)
        }

        if username.characters.count < minCharactersCount {//當字符小于6的時候返回failed
            return .just(.failed(message: "號碼長度至少6個字符"))
        }

        if usernameValid(username) {//檢測本地數據庫中是否已經存在這個名字
            return .just(.failed(message: "賬戶已存在"))
        }

        return .just(.ok(message: "用戶名可用"))
    }

     // 從本地數據庫中檢測用戶名是否已經存在
    funcusernameValid(_username: String) -> 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類應該是這樣子。

classRegisterViewModel{
    //input:
    let username = Variable<String>("")   //初始值為""

    // output:
    let usernameUsable: Observable<Result>

    init() {

    }
}

然后我們在寫我們的 RegisterViewController.swift 文件,viewDidLoad()看起來應該是下面這個樣子

let disposeBag = DisposeBag()

override funcviewDidLoad() {
    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 類中,我們添加下面的代碼

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中我們把它當成obserable,然后對里面的元素進行監聽和處理,這里面我們使用了flatMap,因為我們需要返回一個新的序列,也就是返回處理結果,因為涉及到數據庫操作或者網絡請求(當然是模擬的網絡請求),所以這個序列需要我們去監聽,這種情況我們使用flatMap(具體請參考)

后面使用.shareReplay(1)是因為我們要保證無論多少個Observer來監聽我們這個序列,username的處理代碼我們只執行一次,這一次請求結果供多有的observer去使用。

下面我們在 RegisterViewController 中處理我們的username請求結果。我們在ViewDidLoad中添加下列代碼

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 文件中添加下列代碼

extensionResult{
    var isValid: Bool {
        switch self {
        case .ok:
            return true
        default:
            return false
        }
    }
}

extensionResult{
    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
        }
    }
}

extensionReactivewhereBase:UILabel{
    var validationResult: UIBindingObserver<Base, Result> {
        return UIBindingObserver(UIElement: base) { label, result in
            label.textColor = result.textColor
            label.text = result.description
        }
    }
}

extensionReactivewhereBase:UITextField{
    var inputEnabled: UIBindingObserver<Base, Result> {
        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嘗試一下吧。激動了不??

總結一下這個過程:

輸入文本框->ViewModel中username進行監聽->然后username調用service進行處理->usernameUsable對處理結果進行監聽->提示lable對usernameUsable進行監聽。

哈哈,這就是響應式編程,看起來是不是一路的監聽啊?

password的處理

有了上面的理解,對password的處理我們就輕車熟路了。很簡單了,有些概念我就不需要解釋太多了。

我們現在 Service 中添加對password的處理方法

funcvalidatePassword(_password: String) -> Result {
    if password.characters.count == 0 {
        return .empty
    }

    if password.characters.count < minCharactersCount {
        return .failed(message: "密碼長度至少6個字符")
    }

    return .ok(message: "密碼可用")
}

funcvalidateRepeatedPassword(_password: String, repeatedPasswordword: String) -> Result {
    if repeatedPasswordword.characters.count == 0 {
        return .empty
    }

    if repeatedPasswordword == password {
        return .ok(message: "密碼可用")
    }

    return .failed(message: "兩次密碼不一樣")
}
  • validatePassword處理我們輸入的password
  • validateRepeatedPassword處理我們的密碼確認
  • 上面的返回結果都是一個Result類型的值,因為我們外面不要對這個處理過程進行監聽,所以不必返回一個新的序列

然后我們在 RegisterViewModel.swift 文件中,添加需要的observale

//input:
let password = Variable<String>("")
let repeatPassword = Variable<String>("")

//output:
let passwordUsable: Observable<Result>    //密碼是否可用
let repeatPasswordUsable: Observable<Result> //密碼確定是否正確

然后我們在 RegisterViewController 中,添加passwordTextField綁定

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的輸入

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進行處理

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里面的注冊方法

funcregister(_username: String, password: String) -> Observable<Result> {
    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

//input:
let registerTaps = PublishSubject<Void>()

// output:
let registerButtonEnabled: Observable<Bool>
let registerResult: Observable<Result>
  • registerTaps我們使用了PublishSubject,因為不需要有初試元素,其實前面的Variable都可以換成PublishSubject。大伙可以試試
  • 下面就是注冊按鈕是否可用的輸出,這個其實關系到username和password
  • 最后就是注冊結果的output

進入 RegisterViewController 中,我們添加輸入綁定

registerButton.rx.tap
    .bindTo(viewModel.registerTaps)
    .addDisposableTo(disposeBag)

然后回到 RegisterViewModel 文件中,對button的點擊輸入進行處理,我們添加下面這些代碼,然后我一點點解釋

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進行處理,你需要添加以下代碼

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進行監聽,顯示不同的彈框信息

彈框方法

funcshowAlert(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.swiftLoginViewModel.swift 文件。

有了上面注冊功能的一些代碼工具,我們這邊講解就會比較輕松一點了。

首先在 service 中ValidationService添加下面的代碼

funcloginUsernameValid(_username: String) -> Observable<Result> {
    if username.characters.count == 0 {
        return .just(.empty)
    }

    if usernameValid(username) {
        return .just(.ok(message: "用戶名可用"))
    }
    return .just(.failed(message: "用戶名不存在"))
}

funclogin(_username: String, password: String) -> Observable<Result> {
    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 ,我們寫成下面這樣子

classLoginViewModel{
    // output:
    let usernameUsable: Driver<Result>
    let loginButtonEnabled: Driver<Bool>
    let loginResult: Driver<Result>

    init(input: (username: Driver<String>, password: Driver<String>, loginTaps: Driver<Void>),
        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 中添加下列代碼

override funcviewDidLoad() {
    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

classHero: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類,或者在原來的類中添加方法

class SearchService {
    static let shareInstance = SearchService()

    private init() {}

    func getHeros() -> Observable<[Hero]> {
        let herosString = Bundle.main.path(forResource: "heros", ofType: "plist")
        let herosArray = NSArray(contentsOfFile: herosString!) as! Array<[String: String]>
        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

classContainerViewModel{
    // output:
    var models: Driver<[Hero]>

    init(withSearchText searchText: Observable<String>, 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 類就很簡單了

override funcviewDidLoad() {
    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的一個擴展方法。

我們點進去可以看下,一共有三個items方法,并且上面都有些使用方法,我們使用的這個是

public func items<S : Sequence, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (O) -> (@escaping (Int, S.Iterator.Element, Cell) -> Swift.Void) -> Disposable

這是一個科里化的方法,不帶section的時候使用這個,他有兩個參數,一個是循環利用的cell的identifier,一個cell的類型。后面會返回的是一個閉包,在閉包里對cell進行設置。方法用起來比較簡單,就是有點難理解。

 

來自:http://codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/

 

 本文由用戶 tqka2785 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!