美文网首页
RXSwift内存管理探索

RXSwift内存管理探索

作者: lb_ | 来源:发表于2019-08-09 19:08 被阅读0次

    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<Any>.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<Any>.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<UITouch>, 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<Any>()
    var publicOB : Observable<Any>{
        return mySubject.asObservable()
    }
    
    // 点击屏幕 发出信号
    override func touchesBegan(_ touches: Set<UITouch>, 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<UITouch>, 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<Any>()
    var publicOB : Observable<Any>{
        mySubject = PublishSubject<Any>()
        return mySubject.asObservable()
    }
    

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

    相关文章

      网友评论

          本文标题:RXSwift内存管理探索

          本文链接:https://www.haomeiwen.com/subject/lpccjctx.html