RxSwift重写能量值

基础概念

Observable和Observer

Observable是发生变化的对象。

Observer是接收变化通知的对象。

多个Observer可以订阅同一个Observable。当Observable发生变化时,会通知所有订阅的Observer。

官方说明:

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

创建一个Observable

let observable: Observable = Observable.create { (observer) in

    observer.on(.next(Any))
    observer.on(.completed)

    return Disposables.create {
        //deinit
    }
}

observable.just

let observable = Observable.just(Any)

just(_:)方法可以将对象或值包装成Observable,且不会对值进行任何修改,适用于不会发生变化的对象。一个永远不会发生变化的对象应不应该使用响应式编程还有待商榷。

observable.empty

创建一个空的observable。

bindTo

bindTo是ObservableType协议的重载方法之一,它能将对象与Observable进行绑定,当Observable有事件流发送时,被绑定的对象将会被激活,从而进行相关操作。

textField.rx.text.bindTo(label.rx.text).addDisposableTo(_disposeBag)

开发者可以重载bindTo函数,实现自己的逻辑。

DisposeBag

当一个Observable被订阅后,会创建一个Disposable实例。通过这个实例,我们就能进行资源释放。

RxSwift中的析构分为显式释放和隐式释放:

显式释放是直接在代码中调用函数进行释放,举例:

let dispose = textField.rx.text.subscribe{}
dispose.dispose()

实际开发中并不会这样写。

隐式释放是通过DisposeBag管理,DisposeBag类似于ARC中的@autoreleasepool。

当带有DisposeBag属性的对象调用deinit()时,DisposeBag会被清空,Observer会取消订阅,如果没有DisposeBag会产生retain cycle。

let _disposeBag = DisposeBag()
textField.rx.text.subscribe{}.addDisposableTo(_disposeBag)

实践:重写能量值

从服务端获取能量值总数的代码如下(非响应式):

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)
    }
}

响应式改造,第一步:

删除callback参数,新增函数返回值Disposable,服务端返回数据后observer.on发送事件流。

Disposables.create是Observable被释放时执行的代码。

改造后代码如下:

public func fetchEnergyTotal(uid: String) -> Observable {
    
    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()
        }
    }
}

第二步:创建ViewModel,并添加如下属性:

private let _disposeBag = DisposeBag()

public let power_value: Variable = Variable(0)
public let power_list: Variable<[ListModel]> = Variable([])

调用Service层函数:

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包裹一下,通过value属性赋值取值。

第三步:修改ViewController实现:

先删除tableView.delegate和tableView.dataSource设置,实现的委托函数也一并删除。

添加函数_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)

解释一下:

调用bindTo(_:)将power_list绑定到tableview每一行执行的代码。

调用items(cellIdentifier:cellType:),传入单元格的重用标示和类型,如果tableview有原始的代理,这些函数也会被执行。

传入单元格执行的闭包,闭包的参数会返回行数,绑定的模型,cell对象,这样配置单元格样式就很容易。

最后获取bindTo的Disposable,添加到_disposeBag。

viewModel.power_value
    .asObservable()
    .map { (value) -> String in return "\(value)" }
    .bindTo(totalLabel.rx.text)
    .addDisposableTo(_disposeBag)

订阅能量值总数,通过map函数将Int转为String,绑定给totalLabel,最后加到释放池。

下一步,删除函数 _tipsButtonAction(_:)

修改代码:

tipsButton.rx.tap.subscribe(onNext: { [weak self] in
    let tips = PTEnergyValueTipsVC()
    self?.present(tips, animated: true, completion: nil)
}).addDisposableTo(_disposeBag)

小结

RxSwift能简化异步操作,更容易管理事件,提高代码可读性。

统一上层事件接受,例如:

连上蓝牙设备后,系统会调用委托函数centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral),只有一个PaiBand还好,可以用闭包传递给上层,如果要同时连接多个设备,则要管理多个闭包,增加了开发难度。(虽然可以用通知替代闭包,但上层代码会散落在各处,不优雅。)

能量值是个很简单的列表,无法体现出RxSwift的强大,春节过后抽空重写蓝牙模块。

参考

https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md

https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Why.md

你可能感兴趣的:(RxSwift重写能量值)