17.RxSwift 内存管理(下)

1,324 阅读3分钟
循环引用释放不掉

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, &prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            })
        return bag
    }
}