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<Any> = 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<Int>,服务端返回数据后observer.on发送事件流。

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

改造后代码如下:

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

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

private let _disposeBag = DisposeBag()

public let power_value: Variable<Int> = 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 在之前用Objective-C语言做项目的时候,我习惯性的会利用MVVM模式去架构项目,在框架Reactiv...
    Tangentw阅读 21,307评论 32 124
  • 首先,何为RxSwift? RxSwift是ReactiveX的Swift版本,一个响应式变成框架。传送门 开始之...
    cocoawork丶阅读 3,280评论 0 3
  • 前言 看了前几篇关于RxSwift主要概念的文章,会对RxSwift有个大致的了解,这篇文章会详细讲述如何使用Rx...
    最Fly的Engine人阅读 14,452评论 3 40
  • 当程序员原来越浮躁了,项目做多了大都是雷同的, 对技术没啥帮助,读一些牛逼的第三方框架,有助于提升,关于RxSwi...
    水落斜阳阅读 4,088评论 0 1
  • 为什么要用Rx 传统的编程方式大多都是告诉程序需要做什么、怎么做、什么时候做,并通过KVO、Notificatio...
    康富贵阅读 5,817评论 1 2