RxSwift-爬过的坑
RxSwift
是一个非常好用的框架,如果你喜欢用 Swift
开发,那么 RxSwift
是你不二的选择,函数响应式的结果,让你的代码飞起来!在上瘾 RxSwift
给我们带来的便捷的同时,经常也会出现一些致命的坑,让你怎么也爬不出去,难受的一匹….归其本质:你还是对 RxSwift
不够了解,如果你想玩好 RxSwift
,不妨花点时间静下心来研究一下底层!这一篇文章给大家介绍几点,平时在使用 RxSwift
经常会遇到的坑
RxSwift计数问题
首先有两个页面 LGHomeViewController 首页
和 LGDetialViewController 详情
,详情页面给首页进行传值,我们可以通过序列传递,达到你意想不到的快感,看代码
LGDetialViewController 中
// 内部序列响应,不被外界影响 fileprivate var mySubject = PublishSubject() var publicOB : Observable{ return mySubject.asObservable() } 复制代码
publicOB mySubject
LGHomeViewController 中
override func touchesBegan(_ touches: Set, 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 **************************************** 走了 销毁了 销毁释放 text = Optional("Cooci") *****LGDetialViewController出现了:RxSwift的引用计数: 38 **************************************** 走了 销毁了 销毁释放 text = Optional("Cooci") *****LGDetialViewController出现了:RxSwift的引用计数: 39 **************************************** 复制代码
- 可以清晰的看到: 计数由37-38-39
解决办法
- **思路一:**最直接不加入垃圾销毁袋

- **思路二:**我们分析因为,当前首页没有释放导致首页的销毁垃圾袋不断增多! 换一个垃圾销毁袋

- 思路三: 我们通过前面的销毁流程,可以直接调用
complete
或者error
信号达到 :及时回收,这样就不至于一直存在首页的垃圾袋中
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 页面要退出,及时完成以便调用dispose mySubject.onCompleted() } 复制代码
因为调用序列的完成函数,就会导致我们的序列一次性,下次就无法响应 解决办法:重新激活
fileprivate var mySubject = PublishSubject() var publicOB : Observable{ // 重置激活 mySubject = PublishSubject() 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 { var prepareForReuseKey: Int8 = 0 if let prepareForReuseOB = objc_getAssociatedObject(base, &prepareForReuseKey) as? Observable { 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
,我为自己带盐!