RxSwift(13)— 爬過的坑

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!swift


RxSwift 目錄直通車 --- 和諧學習,不急不躁!bash


RxSwift是一個很是好用的框架,若是你喜歡用Swift開發,那麼RxSwift是你不二的選擇,函數響應式的結果,讓你的代碼飛起來!在上癮RxSwift給咱們帶來的便捷的同時,常常也會出現一些致命的坑,讓你怎麼也爬不出去,難受的一匹....歸其本質:你仍是對RxSwift不夠了解,若是你想玩好RxSwift,不妨花點時間靜下心來研究一下底層!這一篇文章給你們介紹幾點,平時在使用RxSwift常常會遇到的坑app

RxSwift計數問題

首先有兩個頁面LGHomeViewController 首頁LGDetialViewController 詳情,詳情頁面給首頁進行傳值,咱們能夠經過序列傳遞,達到你意想不到的快感,看代碼框架

LGDetialViewController 中ide

// 內部序列響應,不被外界影響
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()
}
複製代碼

由於調用序列的完成函數,就會致使咱們的序列一次性,下次就沒法響應 解決辦法:從新激活post

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度仰望天空,該死!我這無處安放的魅力!

相關文章
相關標籤/搜索