RxSwift内存管理
RxSwift 是函数响应式编程的框架。使用时满屏都是函数、闭包。那么多闭包,岂不是到处都是循环引用的陷阱?怎么来检查是否有内存泄漏呢?
RxSwift 在很多基类的构造函数和析构函数中做了埋点统计。
public class Observable<Element> : ObservableType {
init() {
#if TRACE_RESOURCES
_ = Resources.incrementTotal()
#endif
}
deinit {
#if TRACE_RESOURCES
_ = Resources.decrementTotal()
#endif
}
}
但是,需要定义TRACE_RESOURCES
后才有效。在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
修改后记得pod update
,然后就可以通过RxSwift.Resources.total
来读取当前的引用计数了。
如果引用计数不平衡,就该分析代码有没循环引用的地方,循环引用无非就是我引用你,你引用我,最后扯不开的那种。这里也有闭包中的循环引用方面简单的解决方法。
demo
我们先从一个简单的 demo 中分析下 RxSwift 中的持有关系:
class ViewController: UIViewController {
var disposeBag = DisposeBag()
var name : Any?
override func viewDidLoad() {
super.viewDidLoad()
Observable<Any>.create { (observer) -> Disposable in
observer.onNext("jack")
return Disposables.create()
}.subscribe(onNext: { (e) in
print(e)
}).disposed(by: disposeBag)
}
}
这就是个简单的订阅,ViewController 通常都会用DisposeBag
来管理订阅的生命周期。
持有关系分析
控制器会持有一个DisposeBag
,而订阅中构造的内部类都有对应的Disposable
来负责销毁,Disposable
最后是通过disposed(by:)
来insert
到DisposeBag
中的。所以,Disposable
持有的,控制器都通过DisposeBag
也持有了。
我们需要知道Disposable
都持有了什么,才能避免在使用控制器对象的时候形成闭环引用。
序列的创建
在RxSwift核心逻辑中我们知道,序列在创建的时候其实做的事情很少:
- 创建可观察序列
AnonymousObservable
- 保存
create闭包
这部分只有序列持有着create闭包
,也就是_subscribeHandler
。
那么,在create闭包
使用self
是不会有循环引用问题的。如果self
也持有着序列,就不行了。
序列的订阅
之前学习RxSwift销毁者,对销毁流程已经很熟悉了。订阅函数返回的其实是个BinaryDisposable
。BinaryDisposable
在dispose
的时候,主要是在销毁sink
,其他的Disposable
,有闭包的,在销毁时就顺带回调一下。结合之前所学,可以得出下图的持有关系:
简单来说就是:
-
self
持有bag
-
bag
持有sink
-
sink
持有观察者 - 观察者持有
_eventHandler
-
_eventHandler
中使用self
就需要注意了!
所以,在_eventHandler
中使用self
是很危险的,也是经常需要使用self
的地方,需要破开闭环。
循环引用的问题,除了通常的弱引用外,RxSwift 也有很多种方式都可以销毁,这样循环引用的闭环也就破了:
- 合理使用
DisposeBag
- 发出
.completed
或.error
- 调用
dispose
.takeUntil(deallocated)
平时开发中有很多的造成循环引用的场景,这里就不列举了,对框架源码有一定的了解,再加上程序中自己更加熟悉的代码,可以轻松避免循环引用的问题。