自學Swift有一段時間了,在一個技術群里偶然聽到RxSwift的概念逻卖,了解了以后,覺得很有必要學一學昭抒。但是開始接觸真的比較難理解评也。從網(wǎng)上找了一些資料以后開始了RxSwift之旅。
主要資料參考如下(感謝分享的力量):
http://www.codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/
http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/
博主寫的很好灭返,所以沒必要再照著寫一遍盗迟。這邊文章主要針對上面的博客寫一些自己的理解以及總結(jié)吧。
RxSwift實戰(zhàn)這篇文章中熙含,是基于MVVM寫的罚缕。其中用到的文件有Service,ViewModel怎静,Protocol邮弹,ViewController(頁面比較簡單,并未涉及到View蚓聘。)
Service主要負責一些網(wǎng)絡請求和一些數(shù)據(jù)的訪問操作腌乡,供ViewModel使用。
注冊頁面三個文本框加一個按鈕夜牡,要實現(xiàn)的效果如下与纽。
用戶名和密碼都有一定格式要求。
先把整體的流程貼在這里塘装。
1.將用戶名的TextField流做為Observable(被觀察者)綁定到ViewModel的userName急迂。
其中userName聲明為
let username = Variable<String>("") //初始值為""
Variable是BehaviorSubject一個包裝箱,就像是一個箱子一樣蹦肴,使用的時候需要調(diào)用asObservable()拆箱僚碎,里面的value是一個BehaviorSubject,他不會發(fā)出error事件冗尤,但是會自動發(fā)出completed事件听盖。
Variable和BehaviorSubject都是Subjects概念的范疇胀溺,具體可參考http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/。此處理解為把userName聲明為一個Observable即可皆看。
2.將TextFeld的text綁定到userName上
usernameTextField.rx.text.orEmpty
.bindTo(viewModel.username)
.addDisposableTo(disposeBag)
// usernameTextField.rx.text.orEmpty是RxCocoa庫中概念仓坞,把TextFiled的text變成了一個Observable,orEmpty把String?過濾nil
// 變?yōu)镾tring類型腰吟。
- 在vewModel中處理userName
let usernameUsable: Observable<Result> // 這個是對userName處理以后的輸出output
// 如果只是檢測格式无埃,那么可以寫成如下形式
usernameUsable = userName.asObservable().map.({(userName) -> Result in
if userName.characters.count == 0 {
return .empty
}
if userName.characters.count < characterCount {
return failed(message:"長度至少6個字符")
}
return just(.ok(message:"用戶名可用")
}).shareReplay(1)
// map函數(shù)是通過傳入一個函數(shù)閉包把原來的sequence轉(zhuǎn)變?yōu)橐粋€新的sequence的操作
如果這其中涉及到了網(wǎng)絡請求,或者是耗時的讀取數(shù)據(jù)庫操作毛雇,那么我們會用到flatMap函數(shù)
flatMap將一個sequence轉(zhuǎn)換為一個sequences嫉称,當你接收一個sequence的事件,你還想接收其他sequence發(fā)出的事件的話可以使用flatMap灵疮,她會將每一個sequence事件進行處理以后织阅,然后再以一個新的sequence形式發(fā)出事件。
具體參考:
http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/
如果我們要檢測用戶名是否已經(jīng)存在震捣,需要去發(fā)送網(wǎng)絡請求或者去查詢本地數(shù)據(jù)庫荔棉,那么就要用flatMap
usernameUsable = userName.asObservable().flatMap({(userName) in
return service.validateUsername(username)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(.failed(message: "username檢測出錯")
})
要注意的是
Map 中的閉包返回的是一個經(jīng)過閉包參數(shù)userName(這也是原始序列的元素)處理過后的Result類型的元素。
flatMap中的閉包返回的是一個 Observable<Result> 序列蒿赢,例如
service中validateUsername中返回的 .just(.failed(message: "號碼長度至少6個字符"))
service中validateUsername方法如下
func validateUsername(_ 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) {//檢測本地數(shù)據(jù)庫中是否已經(jīng)存在這個名字
return .just(.failed(message: "賬戶已存在"))
}
return .just(.ok(message: "用戶名可用"))
}
觀察map和flatMap函數(shù)定義
public func map<R>(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable<R>
public func flatMap<O : ObservableConvertibleType>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E>
其實這里的map和flatMap的作用是一樣的润樱。map函數(shù)可以對原有序列里面的事件元素進行改造,返回的還是原來的序列羡棵。而flatMap對原有序列中的元素進行改造和處理壹若,每一個元素返回一個新的sequence,然后把每一個元素對應的sequence合并為一個新的sequence序列皂冰。
觀察fatMap店展,聲明了一個遵循ObservableConvertibleType協(xié)議的泛型類型O(可以理解為一個序列)。閉包中的參數(shù)是原始序列的元素(在這指的是userName:String)灼擂,閉包返回的是遵循ObservableConvertibleType協(xié)議的泛型類型O壁查,也就是說返回的是一個序列。最后函數(shù)返回的是O類型的元素組成的一個序列:Observable<O.E>剔应,在這里睡腿,O.E指的是Result類型,也就是說flatMap函數(shù)最后返回的是O類型的峻贮。
(有理解的不對的地方席怪,歡迎指正)。
viewModel和Service中工作完成以后纤控,可以在controller寫后續(xù)工作了
思考挂捻?如何讓userNameUsable綁定到提示用戶名是否可用的Label呢?
當然可以用subscribe(onNext:)方法
viewModel.userNameUsable.subscribe(onNext: { [weak self] (result) in
switch result {
case .ok(let message):
self!.nameTipLabel.text = message
case .empty:
self!.nameTipLabel.text = ""
case .failed(let message):
self!.nameTipLabel.text = message
}
}).addDisposableTo(disposeBag)
但是這樣寫有一些繁瑣船万,畢竟這些放在viewController里面寫不太優(yōu)雅刻撒,如果要在Result不同情況下顯示不同顏色骨田,那么代碼量又會進一步增多。
想要解決這個声怔,就需要用到UIBindingObserver了态贤,這個是個很有用的東西。
我們現(xiàn)在為UILabel自定義一個validationResult
extension Reactive where Base: UILabel {
var validationResult: UIBindingObserver<Base, Result> {
return UIBindingObserver(UIElement: base) { label, result in
label.textColor = 根據(jù)result處理
label.text = 根據(jù)result處理
}
}
}
// 自定義了一個Observer醋火,對UIlabel進行了擴展悠汽,根據(jù)result結(jié)果,進行他的text和textColor的顯示
詳細內(nèi)容見http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/
然后viewController中可以寫成如下形式芥驳,優(yōu)雅了很多吧柿冲!
viewModel.userNameUsable
.bind(to: nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
RxSwift中除了Observable,還有一個概念是Driver
下面來看一下Driver
其實Driver和Observable的使用結(jié)構(gòu)是一樣的只是Driver和Observable有點區(qū)別兆旬,Driver是RxSwift專門針對UI操作假抄,而Observable是一個通用的東西
下面是登錄頁面的Driver使用
頁面效果如下
同樣先寫Service,這里如果要校驗一下userName是否是已經(jīng)存在的話爵憎,那么要用flatMap慨亲,因為要有耗時操作,如果只是校驗一下格式的話宝鼓,那么要用map。
假設這里需要校驗用戶名是否已存在巴刻。那么
在viewModel中聲明輸入如下:
// input
let userName = Observable<String>("")
// output
let userNameUsable: Driver<Result>
ViewModel的init方法中初始化userNameUsable
userNameUsable = userName.asObservable()
.asDriver(onErrorJustReturn: "")
.flatMapLatest { username in
return service.loginUserNameValid(username)
.asDriver(onErrorJustReturn:
.failed(message: "連接server失敗"))
}
在ViewController里面進行綁定
viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
如果沒有自定義Observer愚铡,那么寫法如下:
viewModel.userNameUsable.drive(onNext: { [weak self] (result) in
switch result {
case let .ok(message):
// 處理
case .empty:
// 處理
case let .failed(message):
// 處理
}).addDisposableTo(disposeBag)
注意:只有Driver可以使用drive方法,Observable是不能使用的胡陪。
另外官方的寫法是這樣的:
viewModel初始化方法如下
init(input: (userName: Driver<String>, password: Driver<String>, loginTaps: Driver<Void>), service: ValidateService)
// init的參數(shù)使用一個元組
viewController里面
let viewModel = LoginViewModel(input: (userName: nameTextField.rx.text.orEmpty.asDriver(), password: passWordTextField.rx.text.orEmpty.asDriver(), loginTaps:doSomething.rx.tap.asDriver() ), service: ValidateService.instance)
viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
還有剩下的列表頁沥寥,使用流程和上述方式并無二致∧可參考博主原文邑雅。
總之還是要多寫。否則看過幾天之后便全忘記了妈经。
最后感謝原文博主分享淮野。