RxSwift 實戰教程-核心用法

2021-03-02 iOS大全

(點擊上方公眾號,可快速關注)

來源:沸沸騰(@請叫我沸沸騰 )

www.jianshu.com/p/089ae5bececa

如有好文章投稿,請點擊 → 這裡了解詳情

前面兩篇關於RxSwift的文章都是一些概念,我估計大夥看了一定是迷迷糊糊的,還是不知道RxSwift怎麼使用,那麼這裡俺就帶領大夥一起去做一個Demo,去實戰一下RxSwift,大夥耐心寫完,理解透徹以後,保證大夥能掌握到RxSwift基本核心用法。

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

Demo

Demo地址是 github.com/CoderTian/LoginWithRxSwift 咱們這個Demo選用了最萬能的登錄註冊功能,先來看下Demo的一些基本演示,並未包含所有細節,

註冊界面

登錄界面

點擊輸入用戶名,如果本地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文件中引入這兩個文件

import RxCocoa

import RxSwift

登錄界面

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

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

建立需要文件

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

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文件中添加下面內容

// 表示我們的一些請求的結果

enum Result {

    case ok(message: String)

    case empty

    case failed(message: String)

}

username的處理

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

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

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類應該是這樣子。

class RegisterViewModel {

    //input:

    let username = Variable("")    //初始值為""

 

    // output:

    let usernameUsable: Observable

 

    init() {

 

    }

}

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

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

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

viewModel.usernameUsable

    .bindTo(usernameLabel.rx.validationResult)

    .addDisposableTo(disposeBag)

viewModel.usernameUsable

    .bindTo(passwordTextField.rx.inputEnabled)

    .addDisposableTo(disposeBag)

上面的validationResult,inputEnabled需要我們自己去定製,這就又用到了RxSwift入坑解讀-那些難以理解的細節文章中的UIBindingObserver了,我們需要創建自己的監聽者。具體大家可以好好參考這篇文章。

所以我們在protocol.swift文件中添加下列代碼

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的處理方法

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

//input:

let password = Variable("")

let repeatPassword = Variable("")

 

//output:

let passwordUsable: Observable    //密碼是否可用

let repeatPasswordUsable: Observable //密碼確定是否正確

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

passwordTextField.rx.text.orEmpty

    .bindTo(viewModel.password)

    .addDisposableTo(disposeBag)

repeatPasswordTextField.rx.text.orEmpty

    .bindTo(viewModel.repeatPassword)

    .addDisposableTo(disposeBag)

然後再轉移到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)

下面轉移到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和確定密碼感受下吧。

彈框方法

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)

}

列表界面

下面是列表界面,限於篇幅的原因,這裡我只寫展現,具體的搜索功能和其他tableView的展現技術,我會在下篇文章中進行分享。

在StoryBoard文件中新建列表界面

然後建立相應的ContainerViewController.swift和ContainerViewModel.swift文件。和Hero.swift文件。將所需的圖片資源導入,下載Demo,Demo中有。

首先編寫我們Hero類

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

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)

    }  

}

然後看下我們的ContainerViewModel

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類就很簡單了

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)  

}

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

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的海洋中自由式吧

覺得本文對你有幫助?請分享給更多人

關注「 iOS大全 」

看更多精選 iOS 技術文章

相關焦點

  • RxSwift 入坑解讀-那些難以理解的細節
    這篇文章接著上篇文章,主要來深入了解一些RxSwift實戰中用到的一些重要知識點,這裡面有很多自己的理解和思考,包含很多網上幾乎收不到的內容,希望會是大家研究官方例子的一個重要參考資料,文章中不免會有些錯誤的地方,也請大家能多多留言交流,一起成長。這兩篇文章過後,準備寫實戰教程,希望大家多多關注吧。還有文章在我博客裡閱讀更有感覺哦。let’s go!
  • Swift語言開發入門視頻教程:playground基本用法
    Swift語言開發入門視頻教程:playground基本用法 蘋果公司在今年的WWDC大會上發布了全新程式語言Swift,Swift語言的發布對於開發者們,尤其是iOS開發者們來說確實是蘋果開發者大會的一大亮點
  • rx5700刷bios秒變rx5700xt!rx5700刷rx5700xt bios圖文教程
    好了,下面裝機之家帶來rx5700刷rx5700xt bios圖文教程,刷BIOS教程步驟十分詳細,來看看吧。 rx5700刷bios秒變rx5700xt!
  • RxSwift的第一印象
    let pasteboard = NSNotificationCenter.defaultCenter().rx_notification("UIPasteboardChangedNotification", object: nil)_ = pasteboard.map { [weak self] (notification: NSNotification) ->
  • 《 Swift 程式語言》中文版教程開源
    很多剛開始學Swift的同學直接去擼蘋果的官方文檔了,其實Github上早已有完整的中文翻譯教程,而且目前還在持續更新中。這個開源項目由 SwiftGGTeam 發起的,他們的目標是打造中國第一Swift翻譯團隊,應該說這個工作量還是不小的。
  • 給 iOS 開發者的 RxSwift(一)
    func registerRx() {    let nickNameValid = nickNameTextField.rx.text.orEmpty        .map { (text) -> Bool in        let tLength = text.characters.count        return
  • 深入理解RxSwift
    nameTextField.rx.text.orEmpty.bind(to: nameSubject).disposed(by: disposeBag)passwordTextField.rx.text.orEmpty.bind(to: passwordSubject).disposed(by: disposeBag)Observable<Bool>.combineLatest
  • 從Java到Swift
    基礎部分1.Swift沒有main函數,這個有點像腳本語言。Swift程序的默認入口是main.swift文件,在iOS應用中,則通常標記了@UIApplicationMain的AppDelegate.swift文件。可以類比到Android中,在AndroidManifest.xml中定義的Application。2.Swift不需要定義行結束符,這個是像腳本語言一樣。
  • 解密RxSwift核心邏輯
    這一篇文章全面解密RxSwift核心流程RxSwift這個優秀的框架,設計的api也是非常精簡,讓陌生的用戶也能非常快速上手_ = Observable<String>.create { (obserber) -> Disposable in obserber.onNext("
  • RxJS 入坑教程
    準備工作首先在 https://github.com/teambition/learning-rxjs clone 項目所需的 seed,本文中所有涉及到 RxJS 的代碼將全部使用 TypeScript 編寫。使用 npm start 啟動 seed 項目,這篇文章中我們將實現以下幾點功能: 1.
  • Swift教程(一)--基礎內容
    本次的教程是基於Swift5.1版本Swift是一個全新的用戶iOS,MacOS,watchOS
  • 【新教學上架】您的ZBrush由您定義 | ZBrush腳本入門和實戰案例教程
    ZBrush腳本入門和實戰案例教程本教學由花月風清老師錄製,從零開始、由淺入深地帶大家學習zscript基礎以及實例代碼的編寫
  • Swift語言入門視頻教程:簡介及開發環境搭建
    Swift語言無疑已是大勢所趨,下面就為大家帶來《Swift語言開發入門視頻教程》系列中的第一章內容:程式語言Swift開發視頻教程:簡介及開發環境搭建下一篇:程式語言Swift入門視頻教程:常量和變量 更多Swift語言編程教程:Swift語言教程本講主要介紹了swift的主要特性,以及如何使用XCode6創建一個
  • ionic2項目實戰教程 - 第2講 動態獲取抽屜分類菜單
    動態獲取抽屜分類關注微信訂閱號:TongeBlog,可查看[ionic2項目實戰]全套教程,還有各種乾貨等著你!
  • Vue入門-實戰教程
    Vue教程整理文章匯總分類連結歡迎閱讀原文直達。Vue2+VueRouter2+Webpack+Axios 構建項目實戰(一)基礎知識概述:http://www.javazhiyin.com/?p=285Vue2+VueRouter2+Webpack+Axios 構建項目實戰(二)配置環境及構建初始項目:http://www.javazhiyin.com/?
  • Angular10教程--4.1 RxJs-創建類及合併類操作符
    (參考文檔:https://rxjs-cn.github.io/learn-rxjs-operators/operators/)tips:通常情況下,我們是不會通過new Observable() 形式去創建一個可觀察對象的,都是使用操作符來創建。這些運算符幾乎允許你基於任何東西來創建一個 observable 。
  • PS實戰教程,學會圖層樣式十大技能!
    但是,很多同學一直學不好圖層樣式這個「神器」,今天萬晨曦老師通過實戰案例,讓你學會圖層樣式十大技能!效果圖:學習重點:通過學習,了解掌握各種圖層樣式的用法和技巧。文章末尾提供視頻操作教程,不想看文字教程,請直接拉到文章底部!
  • Debian中編寫你的第一個Apple Swift程序
    先運行以下命令,將它下載到系統上:$ wget https://swift.org/builds/swift-5.0.1-release/ubuntu1804/swift-5.0.1-RELEASE/swift-5.0.1-RELEASE-ubuntu18.04.tar.gz
  • Swift高階之內存管理
    image內存管理在任何程式語言中都是核心概念。儘管有很多教程解釋了Swift自動引用計數的基本原理,但我發現沒有一個可以從編譯器的角度對其進行解釋。在本文中,我們將學習iOS內存管理,引用計數和對象生命周期等基礎知識之外的內容。讓我們從基礎開始,逐步進入ARC和Swift Runtime的內部,首先思考以下問題:內存是什麼?
  • 【知識星球資源】Python3.5-零基礎-高級-項目實戰最新教程
    -共14章節 第03周-Python3.5-零基礎-高級-項目實戰最新教程-共19章節 第04周-Python3.5-零基礎-高級-項目實戰最新教程-共18章節第05周-Python3.5-零基礎-高級-項目實戰最新教程-共14章節 第06周-Python3.5-零基礎-高級-項目實戰最新教程-共12章節 第07周-Python3.5-零基礎-高級-項目實戰最新教程