---> 上節 【RxSwift 實踐系列 1/3】爲何使用RxSwiftios
RxSwift 是一種編程思想,不是一門語言,學習他最難的部分就是thinking in Reactive Programming :把全部事件看成一個 stream來思考。 它的目的是讓數據/事件流和異步任務可以更方便的序列化處理git
若是把咱們程序中每個操做都當作一個事件: 好比一個TextFiled中的文本改變,一個按鈕的點擊,或者一個網絡請求結束等,每個事件源就能夠當作一個管道,也就是Sequence,好比一個TextFiled,當咱們改變裏邊的文本的時候 這個時候TextFiled就會不斷的發出事件, 從他的Sequence中不斷的流出 咱們只須要監聽這個Sequence,每流出一個事件就作相應的處理。同理UIButton也是一個Sequence 每點擊一次就流出一個事件github
app需求:
一、準備工做:
二、網絡請求準備
三、功能:輸入股票帳號,顯示相關股票
四、添加到關注列表,從網絡獲取股票信息
五、實時顯示關注列表的股票數據狀況
複製代碼
咱們須要建立一個stock的項目,並使用Podfile管理第三方庫。 關於RxSwift的基本使用方法能夠在github的RxSwift查看。sql
Podfile的配置以下:數據庫
platform :ios, '11.0'
use_frameworks!
target 'stock' do
pod 'RxSwift','~> 4.0'
pod 'RxCocoa','~> 4.0' #把UI庫和rx結合
pod 'FMDB', '~>2.6.2' #sqlite數據庫
pod 'SwiftyJSON' #json處理
pod 'Moya/RxSwift' #網絡請求
pod 'ObjectMapper', '~> 3.1' #Json轉模型
pod 'MJRefresh', :inhibit_warnings => true #下拉刷新
pod 'RxDataSources', '~> 3.0' #幫助咱們優雅的使用tableView的數據源方法
pod 'NSObject+Rx' #爲咱們提供 rx_disposeBag
pod 'Then' #提供快速初始化的語法糖
pod 'Reusable' #幫助咱們優雅的使用自定義cell和view,再也不出現Optional
end
#rx的debug使用
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'RxSwift'
target.build_configurations.each do |config|
if config.name == 'Debug'
config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
end
end
end
end
end
複製代碼
經過網絡請求,處理返回數據
知識點:Observable create subscribe disposed moya
複製代碼
thinking in Reactive Programming編程
網絡請求是一個經典的異步請求,moya是基於Alamofire實現的一個網絡請求,它的網絡請求也是一個可訂閱的stream,咱們在此基礎上建立一個可訂閱的模型,把網絡返回結果發送給這個模型,提供給訂閱者展出到uitableview中json
/** 這裏咱們使用moya來作網絡請求,moya的具體使用方法參見[Moya](https://github.com/Moya/Moya) 咱們把moya從網絡api返回的結果當作一個stream,使用rxswift的create把返回結果變成一個可訂閱的信息流 */
//create的方法能夠參見手冊:
create(_ subscribe: @escaping (RxSwift.AnyObserver<Self.E>) -> Disposable)
//主要步驟是:
Observable.create { observer in
observer.onNext(10)
observer.onCompleted()
return Disposables.create()
}
//接下來咱們實現這個從api獲取股票信息的方法,建立了一個可訂閱的數據模型
let netToolProvider = MoyaProvider<StockApi>()
func searchFromApi(repositoryName: String) -> Observable<[SearchModel]> {
return Observable.create { observer in
netToolProvider.rx.request(.SearchStocks(code:repositoryName))
.mapJSON() //Moya RxSwift的擴展方法,能夠把返回的數據解析成 JSON 格式
.subscribe( //訂閱返回的結果
onSuccess:{json in
let info = self.parseSearchResponse(response: json as AnyObject) // 把返回解析成一個listmodel對象
observer.on(.next(info)) //發送next 內容是listmodel
observer.on(.completed) //發送成功
},
onError:{error in
observer.on(.error(error)) //發送錯誤
}).disposed(by: self.bag)//自動內存處理機制
return Disposables.create()
}
}
private func parseSearchResponse(response: AnyObject) -> [SearchModel] {
var info: [SearchModel] = []
let items = response["stocks"] as! [[String:AnyObject]]
let _ = items.filter{
return $0["stock_id"] as! Int != 0 //把退市的去掉
}.map{
info.append(SearchModel(JSON: $0)!)
}
return info;
}
複製代碼
咱們先把這個方法留着,等會咱們會訂閱這個模型,展現到uitableview中去swift
用戶輸入股票編號,調用接口去顯示搜索結果
知識點:RxCocoa ,bind, drive
複製代碼
thinking in Reactive Programming:api
把輸入框事件當作一個流,用戶的輸入就會不斷的發出事件流,咱們只須要監聽事件流就能夠響應用戶的每次輸入。bash
searchFiled.rx.text
.filter{
($0?.lengthOfBytes(using: .utf8))! > 0 //輸入長度大於1才處理數據
}
.throttle(0.5, scheduler: MainScheduler.instance) //延遲0.5秒再執行
.subscribe{ //訂閱這個事件
print($0) //打印用戶的輸入
}.disposed(by: rx.disposeBag)
複製代碼
獲取用戶的輸入後,還須要即時的根據用戶輸入去api獲取搜索結果,對於搜索結果咱們已經在剛纔處理了。如今要作的是用戶輸入和api搜索的事件綁定在一塊兒。這裏rxswift提供了bind方法。
bind,這個方法意圖就是將一個被觀察者與一個指定的觀察者進行綁定,被觀察者事件流中發出的全部事件元素都會讓觀察者接收。
例如:
textField.rx_text
.bindTo(label.rx_text)
.addDisposableTo(disposeBag)
複製代碼
接下來咱們來處理事件流發出者(searchFiled)和觀察者(resultTableView)綁定在一塊兒,當用戶輸入查詢字符串,即時去接口獲取查詢結果,展現在uitableview中:
searchFiled.rx.text
.filter{
($0?.lengthOfBytes(using: .utf8))! > 0 //長度大於1
}
.throttle(0.5, scheduler: MainScheduler.instance) //延遲0.5秒再執行
.flatMap{
ListViewModel().searchFromApi(repositoryName:String(describing: $0!)) //查詢結果,返回可訂閱的SearchModel
}
//事件流發出者(searchFiled)和觀察者(resultTableView)綁定在一塊兒
.bind(to: self.resultTableView.rx.items(cellIdentifier: "SearchTableViewCell", cellType: SearchTableViewCell.self)) {(_, model:SearchModel, cell:SearchTableViewCell) in
cell.nameLabel.text = model.name
cell.codeLabel.text = model.code
}
.disposed(by: rx.disposeBag)
複製代碼
可是若是用bind,會有幾個問題: 1.若是searchFromApi錯誤(鏈接失敗,參數錯誤等),錯誤將會丟失(得不到任何處理),UI也不會再處理和響應任何的結果
2.若是searchFromApi返回的結果不是在主線程,那麼不在子線程中對UI進行綁定,就會出現未知的錯誤
3.若是返回的結果綁定到了兩個UI上,那麼意味着每個UI都要進行一次網絡請求,即進行兩次網絡請求,那麼,咱們能夠稍稍修改一下
咱們能夠這樣修改一下:
searchFiled.rx.text
.filter{
($0?.lengthOfBytes(using: .utf8))! > 0 //長度大於1
}
.throttle(0.5, scheduler: MainScheduler.instance) //延遲0.5秒再執行
.flatMap{
ListViewModel().searchFromApi(repositoryName:String(describing: $0!))//查詢結果,返回可訂閱的SearchModel
.observeOn(MainScheduler.instance) // 將返回結果切換到到主線程上
.catchErrorJustReturn([]) // 若是有問題,錯誤結果將會獲得處理
}
.share(replay: 1)
//事件流發出者(searchFiled)和觀察者(resultTableView)綁定在一塊兒
.bind(to: self.resultTableView.rx.items(cellIdentifier: "SearchTableViewCell", cellType: SearchTableViewCell.self)) {(_, model:SearchModel, cell:SearchTableViewCell) in
cell.nameLabel.text = model.name
cell.codeLabel.text = model.code
}
.disposed(by: rx.disposeBag)
複製代碼
這裏插一句:subscribeOn和observeOn的區別: subscribeOn()設置起點在哪一個線程,observeOn()是設置的後續工做在哪一個線程。
這裏咱們設計的仍是不夠完美,rxswift中提供了drive方法,但是說是專爲UI綁定量身打造
drive方法只能在Driver序列中使用,Driver有如下特色:
1. Driver序列不容許發出error,
2. Driver序列的監聽只會在主線程中。
複製代碼
讓咱們來看看drive的實現方法:
searchFiled.rx.text
.filter{
($0?.lengthOfBytes(using: .utf8))! > 0 //長度大於1
}
.throttle(0.5, scheduler: MainScheduler.instance) //延遲0.5秒再執行
.flatMap{
ListViewModel().searchFromApi(repositoryName:String(describing: $0!))
}
.asDriver(onErrorJustReturn: [])
.drive(resultTableView.rx.items(cellIdentifier: "SearchTableViewCell", cellType: SearchTableViewCell.self)) {
(tableView, element, cell) in
cell.nameLabel.text = element.name
cell.codeLabel.text = element.code
}
.disposed(by: rx.disposeBag)
複製代碼
因此如下狀況你可使用Driver替換BindTo:
一、不能發出error;
二、在主線程中監聽;
三、共享事件流;
複製代碼
本節先到這裏,詳細代碼能夠到這裏:rxStock實例 下載參考
下一節將介紹rxcocoa的使用場景,以及實現這個股票app所用到的更多rxswift方法。