16

RxSwift-爬过的坑

 4 years ago
source link: https://www.tuicool.com/articles/jA3Ur2Z
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RxSwift 是一个非常好用的框架,如果你喜欢用 Swift 开发,那么 RxSwift 是你不二的选择,函数响应式的结果,让你的代码飞起来!在上瘾 RxSwift 给我们带来的便捷的同时,经常也会出现一些致命的坑,让你怎么也爬不出去,难受的一匹....归其本质:你还是对 RxSwift 不够了解,如果你想玩好 RxSwift ,不妨花点时间静下心来研究一下底层!这一篇文章给大家介绍几点,平时在使用 RxSwift 经常会遇到的坑

RxSwift计数问题

首先有两个页面 LGHomeViewController 首页LGDetialViewController 详情 ,详情页面给首页进行传值,我们可以通过序列传递,达到你意想不到的快感,看代码

LGDetialViewController 中

// 内部序列响应,不被外界影响
fileprivate var mySubject = PublishSubject<Any>()
var publicOB : Observable<Any>{
    return mySubject.asObservable()
}
复制代码
publicOB
mySubject

LGHomeViewController 中

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let vc = LGDetialViewController()
    vc.publicOB
    .subscribe(onNext: { (item) in
        print("订阅到 \(item)")
    })
        .disposed(by: disposeBag)
    self.navigationController?.pushViewController(vc, animated: true)
}
复制代码
push

上面的代码乍一看没有什么问题,其实不然,这个过程不断往首页 disposeBag 添加订阅事件,会导致计数不断增加,就是性能消耗

*****LGDetialViewController出现了:RxSwift的引用计数: 37
****************************************
<_01_RxSwift-内存管理.LGDetialViewController: 0x7fa966414de0>走了 
销毁了
<_01_RxSwift-内存管理.LGPerson: 0x600001c8c6c0>销毁释放
text = Optional("Cooci")
*****LGDetialViewController出现了:RxSwift的引用计数: 38
****************************************
<_01_RxSwift-内存管理.LGDetialViewController: 0x7fa96665d280>走了 
销毁了
<_01_RxSwift-内存管理.LGPerson: 0x600001c93780>销毁释放
text = Optional("Cooci")
*****LGDetialViewController出现了:RxSwift的引用计数: 39
****************************************
复制代码
  • 可以清晰的看到: 计数由37-38-39

解决办法

  • **思路一:**最直接不加入垃圾销毁袋
b263IzF.png!web
  • **思路二:**我们分析因为,当前首页没有释放导致首页的销毁垃圾袋不断增多! 换一个垃圾销毁袋
iqumYvj.png!web
  • 思路三: 我们通过前面的销毁流程,可以直接调用 complete 或者 error 信号达到 :及时回收,这样就不至于一直存在首页的垃圾袋中
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 页面要退出,及时完成以便调用dispose
    mySubject.onCompleted()
}
复制代码

因为调用序列的完成函数,就会导致我们的序列一次性,下次就无法响应 解决办法:重新激活

fileprivate var mySubject = PublishSubject<Any>()
var publicOB : Observable<Any>{
     // 重置激活
    mySubject = PublishSubject<Any>()
    return mySubject.asObservable()
}
复制代码

cell复用导致序列重复订阅响应

我们实际开发中避免不了使用 tableView ,那么在使用过程中,经常会有一个坑: cell复用

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! LGTableViewCell
    
    cell.button.rx.tap
        .subscribe(onNext: { () in
            print("点击了 \(indexPath)")
        })
        .disposed(by: bag)
    return cell
}
复制代码
  • 这一段代码乍一看也是没有什么问题的,包括你不滑动屏幕也不会产生问题
  • 只要你一划动屏幕,因为我们的 cell 的重用机制,会导致 cell.button.rx.tap 的订阅也会重复订阅响应,显然不是我们正常开发中想见到的样子
点击了 [0, 0]
********************
点击了 [0, 1]
********************
点击了 [0, 2]
********************
点击了 [0, 1]
点击了 [0, 21]
********************
点击了 [0, 3]
点击了 [0, 23]
********************
点击了 [0, 29]
点击了 [0, 49]
点击了 [0, 69]
********************
复制代码

解决思路

  • 思路一: 把主动销毁的能力收回,销毁垃圾袋交给我们的 cell.disposeBag ,在我们重用响应的时候,及时销毁,重置!
// 外界订阅处理
cell.button.rx.tap
    .subscribe(onNext: { () in
        print("点击了 \(indexPath)")
    })
    .disposed(by: cell.disposeBag)

// cell内部处理
override func prepareForReuse() {
    super.prepareForReuse()
    // 销毁垃圾袋重置
    disposeBag = DisposeBag()
}
复制代码
  • 销毁垃圾袋交给 cell 自身

  • prepareForReuse 响应的时候,销毁垃圾袋重置

  • 效果很明显,问题得到了解决!

  • 思路二:基类封装

class LGCustomCell: UITableViewCell{
var disposeBag = DisposeBag() 
override func prepareForReuse() {
        super.prepareForReuse()

        disposeBag = DisposeBag() 
    }
}
复制代码
  • 把相关重用方法和我们垃圾袋封装在基类,这样就大大节省人力咯!

作为一个牛逼的开发人员,每每想到在 tableView 中处理响应都需要重写 prepareForReuse ,我就觉得难受,此刻我要勇敢的说: RxSwift 其实你可以更好

于是我带着不将就的心态构建我们的 思路三和思路四

extension Reactive where Base: UITableViewCell {
    // 提供给外界重用序列
    public var prepareForReuse: RxSwift.Observable<Void> {
        var prepareForReuseKey: Int8 = 0
        if let prepareForReuseOB = objc_getAssociatedObject(base, &prepareForReuseKey) as? Observable<Void> {
            return prepareForReuseOB
        }
        let prepareForReuseOB = Observable.of(
            sentMessage(#selector(Base.prepareForReuse)).map { _ in }
            , deallocated)
            .merge()
        objc_setAssociatedObject(base, &prepareForReuseKey, prepareForReuseOB, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)

        return prepareForReuseOB
    }
    // 提供一个重用垃圾回收袋
    public var reuseBag: DisposeBag {
        MainScheduler.ensureExecutingOnScheduler()
        var prepareForReuseBag: Int8 = 0
        if let bag = objc_getAssociatedObject(base, &prepareForReuseBag) as? DisposeBag {
            return bag
        }
        
        let bag = DisposeBag()
        objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        
        _ = sentMessage(#selector(Base.prepareForReuse))
            .subscribe(onNext: { [weak base] _ in
                let newBag = DisposeBag()
                guard let base = base else {return}
                objc_setAssociatedObject(base, &prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            })
        return bag
    }
}
复制代码
  • 重用响应 prepareForReuse ,只要发现我们的 cell 已发生重用,通过 RxSwift 就会接受到一个重用序列的响应,起到绑定效果
  • 这里还合并了 cell 的销毁序列,毕竟 cell 都销毁了也就没有任何响应的意义
cell.button.rx.tap.takeUntil(cell.rx.prepareForReuse)
    .subscribe(onNext: { () in
        print("点击了 \(indexPath)")
    })
复制代码
  • 通过 takeUntil 限定了 button 的点击响应能力
cell.button.rx.tap
    .subscribe(onNext: { () in
        print("点击了 \(indexPath)")
    })
    .disposed(by: cell.rx.reuseBag)
复制代码
  • **思路四:**就是通过把此次响应加入到特定的销毁袋,这个销毁袋通过关联属性的方式保证了一定的性能,同时这个销毁袋是观察了 cell 的重写响应,一旦有重写那么就直接销毁重置,达到自动重置效果
  • 思路三&思路四都是基于最初的思路不够构建,目的也是为了: Write once, run anywhere

2019年08月10日 01:33 还在坚持把博客写完,这一年一直在不断更新博客内容(因为之前一直忙还有自己惰性都没有好好更新)看到很多博主都是粉丝几千,内心也难免失落。 悟已往之不谏,知来者之可追! 接下来会持续努力和大家一起共建 iOS 生态强盛。我还是我,颜色不一样的烟火,我是 Cooci ,我为自己带盐!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK