一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情。
- 本文主要介绍RxSwift中的内存管理,以及循环引用情况,和如何避免循环引用
1. 循环引用
通常我们闭包会捕获持有者的话会造成循环引用,打破循环引用通常我们会使用弱引用打破循环
,比如使用[weak self]
,无主引用[unowned self]
,这里有的时候比如会出现逃逸闭包
的情况,比如我们闭包的任务在之后进行。
此时就会发生崩溃,此时self
被释放了,2秒后我们调用闭包。此时我们可以使用【weak self】
修饰,就有了空安全
判读,但是我们self
释放了,无法打印属性名字。这个时候还是要使用强引用
,
我们使用局部变量strongSelf
持有我们的self
,在异步函数闭包作用域内
,内部执行完成后就释放了strongSelf
。
我们可以使用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
2.1 持有序列crate闭包捕获
对于我们序列持有者在crate
的闭包中持有self
,会造成循环引用
其持有链:
self->ob->crate闭包->self
同样的我们可以使用弱引用打破循环
2.2 持有序列subscribe闭包捕获
对于在持有的序列的订阅闭包
中捕获self
。同样造成了循环引用
其持有链:
因为 self->ob序列->subscribe闭包->self
同样的我们可以使用弱引用打破循环
2.3 持有观察者在crate闭包捕获
我们持有observer
,通常我们会赋值全局变量,这样我们可以主动调用on事件
,比如我们在点击屏幕发送12
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闭包捕获
我们在订阅中捕获self
可以发现造成了循环引用
分析下 self-->observer-->eventHandle(订阅闭包)-->self
3.传递中的序列
我们通常会有一些作为参数传递的情况,类似我们oc中的block
,我们可以在详情页
fileprivate var mySubject = PublishSubject<Any>()
var publicOB : Observable<Any>{
return mySubject.asObservable()
}
这样的好处,我们通过转换隐藏内部的具体实现,内部使用mySubject
进行调用,外部则暴漏publicOB
。
我们push的时候给publicOB
进行赋值
我们点击的时候跳转详情页面,返回的时候可以发现页面销毁
了,没什么问题,但是我们可以发现我们Rx资源数量
不断增加,说明我们的序列没有被释放
。我们可以发现publicOB
是首页的垃圾袋进行销毁管理的。因此我们可以
3.1 解决序列销毁方式
- 不关联
disposeBag
因为我们当前的页面 的垃圾袋disposeBag
没有移除,因此不会dispose()
我们不关联当前的页面的垃圾袋则可以结束关联
。
- 关联所在页面的
disposeBag
我们的序列和当前的详情页是同一个生命周期
,因此可以使用vc的disposeBag
take(until:)
我们使用take(until:)
添加序列的判断条件:当页面销毁
的时候结束序列。
- 主动调用
同样的我们在页面要离开的时候主动发送complete
事件从而主动调用dispose()
3.2 循环引用解决
我们如果对vc进行属性持有不重复创建,重复操作,发现我们的订阅没有响应
这里我们可以发现我们的订阅闭包捕获了self,因此造成了循环引用
self->detailVC->pulishOB->eventHandle->self
关于无法响应我们可以看下PublishSubject发送OnComplete
事件后
因此我们可以发现我们之前离开页面的时候发送completd
事件后,下次再发送就无法响应了。
- 解决
我们初始化的时候重新初始化PublishSubject
,在订阅闭包解除捕获强引用。
4. 总结
在RxSwift中我们使用的时候注意是否造成循环引用
,我们可以配合RxSwift.Resources.total
使用打印页面的使用情况。关于闭包的捕获可以看下我之前的文章Swift闭包的多个值捕获