基礎(chǔ)概念
Observable和Observer
Observable是發(fā)生變化的對(duì)象。
Observer是接收變化通知的對(duì)象。
多個(gè)Observer可以訂閱同一個(gè)Observable。當(dāng)Observable發(fā)生變化時(shí),會(huì)通知所有訂閱的Observer。
官方說(shuō)明:
IMHO, I would suggest to more think of this as property of sequences and not separate types because they are represented by the same abstraction that fits them perfectly, Observable sequence.
observable.create
創(chuàng)建一個(gè)Observable
let observable: Observable<Any> = Observable.create { (observer) in
observer.on(.next(Any))
observer.on(.completed)
return Disposables.create {
//deinit
}
}
observable.just
let observable = Observable.just(Any)
just(_:)方法可以將對(duì)象或值包裝成Observable,且不會(huì)對(duì)值進(jìn)行任何修改,適用于不會(huì)發(fā)生變化的對(duì)象。一個(gè)永遠(yuǎn)不會(huì)發(fā)生變化的對(duì)象應(yīng)不應(yīng)該使用響應(yīng)式編程還有待商榷。
observable.empty
創(chuàng)建一個(gè)空的observable。
bindTo
bindTo是ObservableType協(xié)議的重載方法之一,它能將對(duì)象與Observable進(jìn)行綁定,當(dāng)Observable有事件流發(fā)送時(shí),被綁定的對(duì)象將會(huì)被激活,從而進(jìn)行相關(guān)操作。
textField.rx.text.bindTo(label.rx.text).addDisposableTo(_disposeBag)
開(kāi)發(fā)者可以重載bindTo函數(shù),實(shí)現(xiàn)自己的邏輯。
DisposeBag
當(dāng)一個(gè)Observable被訂閱后,會(huì)創(chuàng)建一個(gè)Disposable實(shí)例。通過(guò)這個(gè)實(shí)例,我們就能進(jìn)行資源釋放。
RxSwift中的析構(gòu)分為顯式釋放和隱式釋放:
顯式釋放是直接在代碼中調(diào)用函數(shù)進(jìn)行釋放,舉例:
let dispose = textField.rx.text.subscribe{}
dispose.dispose()
實(shí)際開(kāi)發(fā)中并不會(huì)這樣寫(xiě)。
隱式釋放是通過(guò)DisposeBag管理,DisposeBag類似于ARC中的@autoreleasepool。
當(dāng)帶有DisposeBag屬性的對(duì)象調(diào)用deinit()時(shí),DisposeBag會(huì)被清空,Observer會(huì)取消訂閱,如果沒(méi)有DisposeBag會(huì)產(chǎn)生retain cycle。
let _disposeBag = DisposeBag()
textField.rx.text.subscribe{}.addDisposableTo(_disposeBag)
實(shí)踐:重寫(xiě)能量值
從服務(wù)端獲取能量值總數(shù)的代碼如下(非響應(yīng)式):
public func fetchEnergyTotal(uid: String, callback: @escaping fetchEnergyTotalCalback) {
let URL = "http://test-api-points-system.ptdev.cn/power/search"
var param = [String: Any]()
param["uid"] = Int(uid) ?? 0
param["type"] = "POWER"
JsonRequest(.post, URLString: URL, parameters: param) { (request, result, error) in
guard let code = result?["error_code"] as? Int, code == 0 else {
callback(0, PT_RESPONSE_RESULT_FAILED)
return
}
guard let power = result?["total_power"] as? Int else {
callback(0, PT_RESPONSE_RESULT_FAILED)
return
}
callback(power, 0)
}
}
響應(yīng)式改造,第一步:
刪除callback參數(shù),新增函數(shù)返回值Disposable<Int>,服務(wù)端返回?cái)?shù)據(jù)后observer.on發(fā)送事件流。
Disposables.create是Observable被釋放時(shí)執(zhí)行的代碼。
改造后代碼如下:
public func fetchEnergyTotal(uid: String) -> Observable<Int> {
return .create { (observer) -> Disposable in
let URL = "http://test-api-points-system.ptdev.cn/power/search"
let param = ["uid": Int(uid) ?? 0, "type": "POWER"] as [String: Any]
let request = Alamofire.request(
URL,
method: .post,
parameters: param,
encoding: JSONEncoding.default,
headers: nil).response { response in
if let data = response.data {
let item = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [String: Any]
let power = (item?["total_power"] as? Int) ?? 0
observer.on(.next(power))
observer.on(.completed)
} else {
observer.on(.error(response.error ?? RxCocoaError.unknown))
}
}
return Disposables.create {
request.cancel()
}
}
}
第二步:創(chuàng)建ViewModel,并添加如下屬性:
private let _disposeBag = DisposeBag()
public let power_value: Variable<Int> = Variable(0)
public let power_list: Variable<[ListModel]> = Variable([])
調(diào)用Service層函數(shù):
public func search() {
Request.shared.search().subscribe { [weak self] (event) in
if case let .next(power) = event {
self?.power_value.value = power
}
}.addDisposableTo(_disposeBag)
}
public func fetchList() {
Request.shared.getList().subscribe(onNext: { [weak self] (list) in
self?.power_list.value.append(contentsOf: list)
}).addDisposableTo(_disposeBag)
}
所有需要被訂閱的值都需要用Variable包裹一下,通過(guò)value屬性賦值取值。
第三步:修改ViewController實(shí)現(xiàn):
先刪除tableView.delegate和tableView.dataSource設(shè)置,實(shí)現(xiàn)的委托函數(shù)也一并刪除。
添加函數(shù)_setupCell(),代碼如下:
viewModel.power_list
.asObservable()
.bindTo(tableView.rx
.items(cellIdentifier: "cellIdentifier", cellType: PTEnergyValueTableCell.self)) {
row, data, cell in
let date = Date(timeIntervalSince1970: TimeInterval(data.log_time ?? "0") ?? 0)
let formatter1 = DateFormatter()
formatter1.dateFormat = "MM/dd"
let formatter2 = DateFormatter()
formatter2.dateFormat = "hh:mm"
var point = data.point ?? "0"
if point.hasPrefix("-") == false {
point = "+\(point)"
}
cell.dateLabel.text = date.isToday() ? "今天" : formatter1.string(from: date)
cell.timeLabel.text = formatter2.string(from: date)
cell.contentLabel.text = data.des
cell.valueLabel.text = point
cell.iconView.image = UIImage(named: "icon_30_36")
}.addDisposableTo(_disposeBag)
解釋一下:
調(diào)用bindTo(_:)將power_list綁定到tableview每一行執(zhí)行的代碼。
調(diào)用items(cellIdentifier:cellType:),傳入單元格的重用標(biāo)示和類型,如果tableview有原始的代理,這些函數(shù)也會(huì)被執(zhí)行。
傳入單元格執(zhí)行的閉包,閉包的參數(shù)會(huì)返回行數(shù),綁定的模型,cell對(duì)象,這樣配置單元格樣式就很容易。
最后獲取bindTo的Disposable,添加到_disposeBag。
viewModel.power_value
.asObservable()
.map { (value) -> String in return "\(value)" }
.bindTo(totalLabel.rx.text)
.addDisposableTo(_disposeBag)
訂閱能量值總數(shù),通過(guò)map函數(shù)將Int轉(zhuǎn)為String,綁定給totalLabel,最后加到釋放池。
下一步,刪除函數(shù) _tipsButtonAction(_:)
修改代碼:
tipsButton.rx.tap.subscribe(onNext: { [weak self] in
let tips = PTEnergyValueTipsVC()
self?.present(tips, animated: true, completion: nil)
}).addDisposableTo(_disposeBag)
小結(jié)
RxSwift能簡(jiǎn)化異步操作,更容易管理事件,提高代碼可讀性。
統(tǒng)一上層事件接受,例如:
連上藍(lán)牙設(shè)備后,系統(tǒng)會(huì)調(diào)用委托函數(shù)centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral),只有一個(gè)PaiBand還好,可以用閉包傳遞給上層,如果要同時(shí)連接多個(gè)設(shè)備,則要管理多個(gè)閉包,增加了開(kāi)發(fā)難度。(雖然可以用通知替代閉包,但上層代碼會(huì)散落在各處,不優(yōu)雅。)
能量值是個(gè)很簡(jiǎn)單的列表,無(wú)法體現(xiàn)出RxSwift的強(qiáng)大,春節(jié)過(guò)后抽空重寫(xiě)藍(lán)牙模塊。
參考
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Why.md