RXSwift内存管理探索

RxSwift 中由于大量闭包的存在, 我们不可避免的要考虑循环引用的内存问题. 因此,熟练掌握 RxSwift 内存管理原理, 可以帮助我们在写序列/订阅闭包时明白,何时需要使用 unowned/weak 来避免内存泄漏.何时并不会产生内存泄漏. 不需要处理

Swift 基础内存管理浅探

  • 示例
var myClosure: (() -> Void)?  //vc属性

myClosure = {
    print("\(self.name)")
}
        
myClosure?()

self->myClosure->self 引用链发现必然会造成循环引用.

  • 解决办法
//使用  weak
myClosure = { [weak self] in
    print("\(self?.name)")
}
//使用  unowned
myClosure = { [unowned self] in
    print("\(self.name)")
}

那么 unownedweak 的区别是什么呢?

  • 使用 unowned
myClosure = { [unowned self] in
    DispatchQueue.global().asyncAfter(deadline: .now()+2, execute: {
        print("\(self.name)")
    })
}

运行结果: 崩溃 ,日志为
Fatal error: Attempted to read an unowned reference but the object was already deallocated

  • 使用 weak
myClosure = { [weak self] in
    DispatchQueue.global().asyncAfter(deadline: .now()+2, execute: {
        print("\(self?.name)")
    })
}

运行结果: 打印 nil

unowned访问已经释放的对象时会崩溃
weak 会打印nil,不会崩溃.

但是上面这种情况 (当页面销毁延迟两秒再去访问其属性), 上述这两种显然都不满足. 怎么解决呢?

myClosure = { [weak self] in
    guard let strongSelf = self else { return }
    DispatchQueue.global().asyncAfter(deadline: .now()+2, execute: {
        print("\(strongSelf.name)")
    })
}

著名方法: 强弱共舞.
那么在 RX 的世界中,我们什么时候需要考虑循环引用? 该做如何处理呢?

RxSwift中的内存管理

首先不得不提的是 RxSwift 大多子类都自己实现了引用计数, 用来帮助开发者检查序列订阅内存问题.

init(){
   _ = Resources.incrementTotal()
}

deinit{
   _ = Resources.decrementTotal()
}

提示: 使用引用计数 需要在pod File中引入如下:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    if target.name == 'RxSwift'
      target.build_configurations.each do |config|
        if config.name == 'Debug'
          config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
        end
      end
    end
  end
end

然后pod安装, build一下 run.

那么接下来 举几个内存管理的例子:

案例1
  • 示例
self.textFiled.rx.text.orEmpty
.subscribe(onNext: { (text) in
        self.title = text
})
.disposed(by: disposeBag)
  • 引用链:
    self -> textFiled -> subscribe -> self 很明显是有的. 如何解决呢?
self.textFiled.rx.text.orEmpty
.subscribe(onNext: { [weak self] (text) in
        self?.title = text
})
.disposed(by: disposeBag)
案例2
  • 示例
self.observable = Observable.create({ (observer) -> Disposable in
    observer.onNext("Hello")
    return Disposables.create()
})

self.observable?.subscribe(onNext: { (value) in
    print(self.name)
}).disposed(by: disposeBag)
  • 引用链 :
    self 持有 observable, 在 subscribe 订阅时会创建 observer 持有 onNext 闭包。然后 observer 会被传递给 sinksink 又会将自身封装为 AnyObserver 回传给 create 闭包,作为 observer 参数。
    self -> observable -> subscribe -> observer -> onNext{} -> self通过持有链分析,也能很清晰的发现确实是循环引用的。

  • 解决: 同上, 使用 [weak self]

案例3
  • 示例
Observable.create { (observer) -> Disposable in
    self.observer = observer
    observer.onNext("Hello")
    return Disposables.create()
}
.subscribe(onNext: { (value) in
    print(self.name)
})
.disposed(by: disposeBag)
  • 引用链:
    self -> observer -> onNext{} -> self

RxSwift 实际开发场景中几种内存管理方案

  • 不同VC之间响应时 内存管理方案

场景: vc1vc2 两个 vc . 1跳转进2 . 1订阅2 , 2发送事件.

vc1:

// 点击屏幕 push到控制器2 
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
    
    let vc = NextViewController()
    
    // 订阅控制器2中的可观察序列
    vc.publicOB.subscribe(onNext: { (value) in
        print("\(value)")
    })
    .disposed(by: disposeBag)
    
    self.navigationController?.pushViewController(vc, animated: true)
}

vc2:

// 创建外部可订阅的序列
fileprivate var mySubject = PublishSubject()
var publicOB : Observable{
    return mySubject.asObservable()
}

// 点击屏幕 发出信号
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
    mySubject.onNext("RxSwift")
}

deinit{
    print("来了")
}

运行 , 跳转过去,点击屏幕,响应, 返回 vc2: 来了. 好像没问题?

使用上述我们讲的 RxSwift 的引用计数来检查下, viewDidAppear 等生命周期函数中加入打印当前引用计数, 发现 跳转一次2 就会叠加一次, 意味着我们的订阅并没有释放.

怎么解决呢?

  • 方法1:
vc.publicOB.subscribe(onNext: { (value) in
    print("\(value)")
})
.disposed(by: vc.disposeBag)

bag 在控制其中只是一个属性的角色
这个要理解, 之前写法中我们将订阅释放写入了外面的 vcbag 中, 而 vc1bag 并没有释放, 因此我们改为写入 vc2bag 中. 当vc2 释放, bag 被销毁, bag中的序列订阅也同样被销毁.

  • 方法2:
_ = vc.publicOB.takeUntil(vc.rx.deallocated).subscribe(onNext: { (value) in
    print("\(value)")
})

使用 takeUntil , 意味着该序列订阅保存到 什么什么时候 会销毁.
我们传递 vc.rx.deallocated , 其实跟第一种方法 原理是相同的.

  • 方法3:
    vc2 作为一个 vc1 的一个属性时, 也就意味着 vc2 并不会销毁, 该如何处理内存问题呢.
// vc1
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
    
    print("RxSwift计数: \(RxSwift.Resources.total)")

    // 控制器1 持有 控制器2    
    self.vc.publicOB.subscribe(onNext: { (value) in
        print("\(value)")
    })
        .disposed(by: disposeBag)
    
    self.navigationController?.pushViewController(vc, animated: true)
}
// vc2
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    // 页面即将消失时发送 `completed` 事件
    mySubject.onCompleted()
}

解决方式就是在 vc2viewWillDisappear 时, 调用 onCompleted ,查看源码会发现当发送 .complete 事件时, rx 内部会自动调用 disposable.dispose(). 其实本来就该如此, 序列订阅事件完毕 或者有错时 会销毁, 不再响应.

那么这样也会带来一个问题:
当再进入 vc2 , 再发送响应, 会不能响应, 因为 已经被销毁了. 这时就应该在

fileprivate var mySubject = PublishSubject()
var publicOB : Observable{
    mySubject = PublishSubject()
    return mySubject.asObservable()
}

也就是说 vc1 每次订阅时都产生一个新的 subject . 这样就在不会产生循环引用的前提下, 持续响应 vc2 的事件了.

你可能感兴趣的:(RXSwift内存管理探索)