RxSwift学习-17-RxSwift中的内存管理

937 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情

  • 本文主要介绍RxSwift中的内存管理,以及循环引用情况,和如何避免循环引用

1. 循环引用

通常我们闭包会捕获持有者的话会造成循环引用,打破循环引用通常我们会使用弱引用打破循环,比如使用[weak self],无主引用[unowned self],这里有的时候比如会出现逃逸闭包的情况,比如我们闭包的任务在之后进行。

Pasted Graphic.png 此时就会发生崩溃,此时self被释放了,2秒后我们调用闭包。此时我们可以使用【weak self】修饰,就有了空安全判读,但是我们self释放了,无法打印属性名字。这个时候还是要使用强引用

我们使用局部变量strongSelf持有我们的self,在异步函数闭包作用域内,内部执行完成后就释放了strongSelf

Pasted Graphic 1.png 我们可以使用guard 守卫模式优雅一些

var myClousre:(()->())?


self.myClousre = { [weak self] in

print(self?.name as Any)

guard let strongSelf = self else {

return

}

DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {

strongSelf.name = "jackLove"

print(strongSelf.name as Any)

}

}

self.myClousre!()

2.RxSwift中的内存管理

RxSwift中大量的闭包的调用,因此避免不了导致一些循环引用导致无法释放。那么如何避免内存泄露呢?RxSwift对我们的观察者序列 设计了一套自己的引用计数。

比如我们的序列

public class Observable<Element> : ObservableType {

init() {

#if TRACE_RESOURCES

_ = Resources.incrementTotal()

#endif

}

public func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {

rxAbstractMethod()

}

public func asObservable() -> Observable<Element> { self }

deinit {

#if TRACE_RESOURCES

_ = Resources.decrementTotal()

#endif

}

}

init deinit中对引用计数的增减,同样对于匿名观察者

final class AnonymousObserver<Element>: ObserverBase<Element> {

typealias EventHandler = (Event<Element>) -> Void

private let eventHandler : EventHandler

init(_ eventHandler: @escaping EventHandler) {

#if TRACE_RESOURCES

_ = Resources.incrementTotal()

#endif

self.eventHandler = eventHandler

}



override func onCore(_ event: Event<Element>) {

self.eventHandler(event)

}

#if TRACE_RESOURCES

deinit {

_ = Resources.decrementTotal()

}

#endif

}

因此我们可以通过打印RxSwift.Resources.total表示当前的RxSwift资源使用情况。当然我们也可以使用当前页面的deinit打印是否释放,只是通过RxSwift.Resources.total我们可以快速定位到我们关于RxSwift造成的内存泄露情况。
在podfile中导入下面代码

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

image.png

2.1 持有序列crate闭包捕获

对于我们序列持有者在crate的闭包中持有self,会造成循环引用

Pasted Graphic 2.png 其持有链: self->ob->crate闭包->self

同样的我们可以使用弱引用打破循环

2.2 持有序列subscribe闭包捕获

对于在持有的序列的订阅闭包中捕获self。同样造成了循环引用

Pasted Graphic 2.png 其持有链: 因为 self->ob序列->subscribe闭包->self 同样的我们可以使用弱引用打破循环

2.3 持有观察者在crate闭包捕获

我们持有observer,通常我们会赋值全局变量,这样我们可以主动调用on事件,比如我们在点击屏幕发送12

Pasted Graphic 5.png

Observable<Int>.create { ober in

self.observer = ober

return Disposables.create()

}.subscribe(onNext: {[unowned self] in

print(self.name as Any)

print($0)})

.disposed(by: disposeBag)

可以发现没有造成循环引用

self--\-> ob-->crate闭包->self 我们没有持有序列所以没有造成循环引用

2.4 持有观察者在subscribe闭包捕获

image.png

我们在订阅中捕获self

可以发现造成了循环引用

分析下 self-->observer-->eventHandle(订阅闭包)-->self

3.传递中的序列

我们通常会有一些作为参数传递的情况,类似我们oc中的block,我们可以在详情页

fileprivate var mySubject = PublishSubject<Any>()

    var publicOB : Observable<Any>{

        return mySubject.asObservable()

    }

这样的好处,我们通过转换隐藏内部的具体实现,内部使用mySubject进行调用,外部则暴漏publicOB

image.png

我们push的时候给publicOB进行赋值

image.png

我们点击的时候跳转详情页面,返回的时候可以发现页面销毁了,没什么问题,但是我们可以发现我们Rx资源数量不断增加,说明我们的序列没有被释放。我们可以发现publicOB是首页的垃圾袋进行销毁管理的。因此我们可以

3.1 解决序列销毁方式

  • 不关联disposeBag

image.png

因为我们当前的页面 的垃圾袋disposeBag没有移除,因此不会dispose()我们不关联当前的页面的垃圾袋则可以结束关联

  • 关联所在页面的disposeBag

image.png

我们的序列和当前的详情页是同一个生命周期,因此可以使用vc的disposeBag

  • take(until:) 我们使用take(until:)添加序列的判断条件:当页面销毁的时候结束序列。

image.png

  • 主动调用

同样的我们在页面要离开的时候主动发送complete事件从而主动调用dispose()

image.png

3.2 循环引用解决

我们如果对vc进行属性持有不重复创建,重复操作,发现我们的订阅没有响应

image.png

这里我们可以发现我们的订阅闭包捕获了self,因此造成了循环引用 self->detailVC->pulishOB->eventHandle->self 关于无法响应我们可以看下PublishSubject发送OnComplete事件后

image.png 因此我们可以发现我们之前离开页面的时候发送completd事件后,下次再发送就无法响应了。

  • 解决

image.png

我们初始化的时候重新初始化PublishSubject,在订阅闭包解除捕获强引用。

4. 总结

在RxSwift中我们使用的时候注意是否造成循环引用,我们可以配合RxSwift.Resources.total使用打印页面的使用情况。关于闭包的捕获可以看下我之前的文章Swift闭包的多个值捕获