1. 程式人生 > IOS開發 >17.RxSwift 記憶體管理(下)

17.RxSwift 記憶體管理(下)

迴圈引用釋放不掉

self -> myClosure -> {} -> self -釋放不掉

myClosure = {() in
   //// weak - strong - dance   
DispatchQueue.global().asyncAfter(deadline: .now()+2,execute: {
                self.name = "ABC"
                print(self.name)
            })
        }
self.myClosure!()
複製程式碼

上面的程式碼會產生迴圈引用釋放不掉,解決辦法:

  1. [weak self]
  2. [unowned self]
  3. guard let self = self else { return }

那是不是所有的閉包裡面引用self就會產生迴圈引用? 答案是NO,一定要構成迴圈引用圈才會

RxSwift.Resources.total

通過RxSwift.Resources.total的引用計數檢測是否釋放: 在ViewController裡有下列程式碼:

HomeViewController裡有下列程式碼:

在點選跳轉的過程,兩次跳轉的列印資訊如下:

第二次後,引用計數翻倍了,說明ViewController沒有釋放,那是因為self.title = text
的時候產生了迴圈引用,這個時候加個[weak self]就解決了:

RxSwift 記憶體管理常見問題
  • 持有序列
  1. self -> observable -> 建立閉包 -> self 即create閉包引用self會產生迴圈引用

  2. self -> observable -> subscribe閉包 -> self 即訂閱閉包引用self也會產生循序引用

  • 觀察者持有
  1. self -> disposeBag -> insert -> dispose -> sink -> observer <- self
    即我們在儲存訂閱者的時候不會產生迴圈使用
  2. self -> observer -> observer = AnonymousObservableSink.on() -> AnonymousObservableSink._observer.on -> AnonymousObserver.on -> _eventHandler -> onNext閉包 -> self 即我們在訂閱流程裡列印self也會產生迴圈引用
  • 傳遞中的序列 會產生引用計數的不斷增加
  1. 交給源銷燬控制器的垃圾袋
  2. 序列週期伴隨控制器一起
  3. 不新增垃圾袋
  4. 如果有持有關係 需要及時通過error,completed回收,如果下次想用,在進行啟用

TableViewCellbutton點選複用的問題
let bag = DisposeBag()
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
}
複製程式碼

上面的程式碼會有什麼問題呢?

那怎麼會出現上面這種情況呢,明明已經.disposed(by: bag)了,怎麼還會重用呢,注意:這裡的bag的生命週期是伴隨viewController的,這裡使用bag自然就沒有任何效果

猜想,那使用vcbag不可以,那我們可以直接使用cellbag麼,如下:

//cell裡邊
var disposeBag = DisposeBag()
//vc裡邊
 cell.button.rx.tap
        .subscribe(onNext: { () in
            print("點選了 \(indexPath)")
        })
        .disposed(by: cell.disposeBag)
複製程式碼

執行程式碼:

還是沒有解決,這是為啥呢,原因是我們的cell滾動過程並沒有走deinit,自然也就不會銷燬,因此,即便有disposeBag,不銷燬任然也是沒有用的

那我們vc裡重用佇列裡取cell的地方加上cell.disposeBag = DisposeBag(),能解決問題麼

感覺很完美的解決了問題,但是這種寫法會有嚴重的問題,在不斷的建立cell.disposeBag,滾動的過程也在不斷建立,浪費資源,浪費效能

那要怎樣才能解決該問題呢?

  1. 剛才這種想法是好的,那要是能換個地方初始化cell.disposeBag,能完美解決問題還沒有上面的問題就好了,那我們可以在cell的內部重寫prepareForReuse的時候初始化disposeBag就能完美解決問題了
//cell 準備重用的方法
override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }
複製程式碼
  1. 我們還可以使用takeUntil並擴充套件一個rxprepareForReuse方法 來解決問題
cell.button.rx.tap.takeUntil(cell.rx.prepareForReuse)
        .subscribe(onNext: { () in
            print("點選了 \(indexPath)")
        })
複製程式碼
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
        }
        // sentMessage 響應方法執行之前
        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,newBag,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            })
        return bag
    }
}

複製程式碼