在
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)")
}
那么 unowned
和 weak
的区别是什么呢?
- 使用
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
会被传递给sink
,sink
又会将自身封装为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之间响应时 内存管理方案
场景: vc1
和 vc2
两个 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
在控制其中只是一个属性的角色
这个要理解, 之前写法中我们将订阅释放写入了外面的 vc
的 bag
中, 而 vc1
的 bag
并没有释放, 因此我们改为写入 vc2
的 bag
中. 当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()
}
解决方式就是在
vc2
的viewWillDisappear
时, 调用onCompleted
,查看源码会发现当发送.complete
事件时,rx
内部会自动调用disposable.dispose()
. 其实本来就该如此, 序列订阅事件完毕 或者有错时 会销毁, 不再响应.
那么这样也会带来一个问题:
当再进入 vc2
, 再发送响应, 会不能响应, 因为 已经被销毁了. 这时就应该在
fileprivate var mySubject = PublishSubject()
var publicOB : Observable{
mySubject = PublishSubject()
return mySubject.asObservable()
}
也就是说 vc1
每次订阅时都产生一个新的 subject . 这样就在不会产生循环引用的前提下, 持续响应 vc2
的事件了.