RxSwift(13)—— 爬过的坑

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!


RxSwift目录直通车--- 和谐学习,不急不躁!


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

解决办法
  • 思路一:最直接不加入垃圾销毁袋
  • 思路二:我们分析因为,当前首页没有释放导致首页的销毁垃圾袋不断增多!
    换一个垃圾销毁袋
  • 思路三: 我们通过前面的销毁流程,可以直接调用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,我为自己带盐!

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,724评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,104评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,142评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,086评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,076评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,914评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,220评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,871评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,318评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,834评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,951评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,574评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,162评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,162评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,383评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,349评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,652评论 2 343

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,087评论 1 32
  • 本文章内部分图片资源来自RayWenderlich.com 本文结合自己的理解来总结介绍一下RxSwift最基本的...
    FKSky阅读 2,853评论 4 14
  • RxSwift是由序列,观察者,调度者,销毁者组成。可见,销毁者在RxSwift的重要性。了解销毁者,才能更好的了...
    king_jensen阅读 648评论 0 2
  • 聆寻/文 顺天有个人叫马子才,人们都知他对菊花情有独钟,常常为了新菊种不拒千里去四处求购。 一天,他揣着在金陵购得...
    聆寻阅读 599评论 0 1
  • 干红盛,枯黄落,听怕深秋吹角。衣忒薄,夜风寒,那知桐叶残。 人不易,天未白,缱绻更为远客。一片片,一声声,凄清燕赵行。
    林香砌阅读 306评论 2 6