RxSwift之内存管理
关于内存管理,小伙伴们一定对闭包导致的循环引用有过了解。而RxSwift中大量使用到了闭包回调,那么RxSwift中该如何进行内存管理呢?
说明:本文中的示例代码只是关键代码,其余代码请自行脑补
一、swift闭包
如下面这段代码:
var myClosure: (() -> Void)?
myClosure = {
print("\(self.name)")
}
myClosure?()
在上面这段代码中,myClosure和name是
viewController的一个属性。
很明显,这是会引起内存泄漏的闭包循环引用self->myClosure->self
。
要打破这种循环引用也很简单。
1.使用 weak 关键字。
myClosure = { [weak self] in
print("\(self?.name)")
}
2.使用 unowned 关键字。
myClosure = { [unowned self] in
print("\(self.name)")
}
使用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
使用 unowned 关键字结果会崩溃,那么使用 weak 关键字呢?
myClosure = { [weak self] in
DispatchQueue.global().asyncAfter(deadline: .now()+2, execute: {
print("\(self?.name)")
})
}
执行结果虽然不会崩溃,但是打印出来的值是 nil,也和我们的期望不一致。
所以,在延迟调用这种情况下,只使用weak、unowned 都不能完美结局这个问题。那就没办法解决了吗?
myClosure = { [weak self] in
guard let strongSelf = self else { return }
DispatchQueue.global().asyncAfter(deadline: .now()+2, execute: {
print("\(strongSelf.name)")
})
}
执行可以打印出 name
的值。使用 let strongSelf = self
将弱引用转为强引用,则可以完美解决。这就是著名的 weak-strong dance。
前面对swift的Closure引起的内存管理问题进行了简单的回顾,那么给小伙伴们留一个问题:是不是所有的Closure都需要使用 weak 弱引用?
二、RxSwift中的内存管理
示例1:UI订阅
self.textFiled.rx.text.orEmpty
.subscribe(onNext: { (text) in
self.title = text
})
.disposed(by: disposeBag)
示例1会有循环引用吗?我们来分析一下:
self -> textFiled -> subscribe -> self
,通过持有链分析,确实会有循环引用问题,通过执行验证,controller确实没有销毁。所以需要使用 weak 弱引用。
示例二:UI绑定
self.textFiled.rx.text.orEmpty
.bind(to: self.rx.title)
.disposed(by: disposeBag)
实例二会有循环引用吗?执行发现,并没有循环引用产生。因为 self.rx.title仅作为参数,并没有被引用。所以不会有循环引用问题。
示例三:持有序列,序列订阅中持有self
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)
从执行结果来看,controller无法销毁,产生了循环引用。那我们来分析一下持有链。
在 subscribe
订阅时会创建 observer
持有 onNext
闭包。然后 observer
会被传递给 sink
,sink
又会将自身封装为 AnyObserver
回传给 create
闭包,作为 observer
参数。
self -> observable -> subscribe -> observer -> onNext{} -> self
通过持有链分析,也能很清晰的发现确实是循环引用的。
示例4:持有序列,创建序列中持有self
self.observable = Observable<Any>.create({ (observer) -> Disposable in
observer.onNext("Hello")
print(self.name)
return Disposables.create()
})
self.observable?.subscribe(onNext: { (value) in
print("\(value)")
})
.disposed(by: disposeBag)
self -> observable -> create{} -> self
分析持有链,示例中的代码也会循环引用。
示例5:持有观察者
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 = observer
会产生循环引用吗?
我们知道,在创建序列时 create
闭包中持有了 self
。但是 self
并没有持有,create
创建的序列。所以并不会产生循环引用。
那么在订阅时,是否是也不会产生循环引用呢?
我们在示例4中已经大概分析过 subscribe
的流程,所以我们知道 observer
中会保存 subscribe
中的 onNext
闭包,而 observer
又被 self
所持有了,那么就会形成如下持有链 self -> observer -> onNext{} -> self
所以必然会形成循环引用。
经过上面5个示例的分析,小伙伴们一定对RxSwift什么时候会产生循环引用已经有了一个大致的了解。但是很多小伙伴肯定也会有疑问,平时自己在项目中,并不会如上面的示例中这样使用RxSwift。那么RxSwift的实际项目使用中,我们又该如何进行RxSwift内存管理呢?
三、RxSwift内存管理实战
使用场景:控制器间响应,控制器1订阅响应,控制器2发出信号。
控制器2 代码:
// 创建外部可订阅的序列
fileprivate var mySubject = PublishSubject<Any>()
var publicOB : Observable<Any>{
return mySubject.asObservable()
}
// 点击屏幕 发出信号
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
mySubject.onNext("RxSwift")
}
控制器1 代码:
// 点击屏幕 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)
}
执行代码,完美,Perfect!控制器2 释放了,没有循环引用。似乎一切都是那么恰到好处。
那么我们再使用RxSwift自身的计数来验证一下。
控制器1 代码:
// 点击屏幕 push到控制器2
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 打印 RxSwift计数
print("RxSwift计数: \(RxSwift.Resources.total)")
let vc = NextViewController()
// 订阅控制器2中的可观察序列
vc.publicOB.subscribe(onNext: { (value) in
print("\(value)")
})
.disposed(by: disposeBag)
self.navigationController?.pushViewController(vc, animated: true)
}
再次执行,会发现,每次push再pop后,虽然 控制器2 被销毁了,但是RxSwift计数会增加1,这表示虽然控制器2被销毁了,没有产生循环引用,但是 publicOB
的订阅然后存在,没有取消。
那么,这就是不完美的,那应该如何解决呢?
我们再来分析上面的代码。
在RxSwift核心之Disposable中已经分析过,当 self.disposeBag
自动销毁时,disposeBag
中的所有订阅关系都会被取消,那么在上面的代码中,我们是将对 vc.publicOB
的订阅加入到 控制器1 的 disposeBag
中的。那么在 控制器1 的 disposeBag
销毁前,其内部的订阅都不会取消。所以,当 控制器1 push到 控制器2 再pop回到 控制器1 时,控制器1 的 disposeBag
并没有被销毁,所以对 vc.publicOB
的订阅关系仍然存在。
分析了 RxSwift计数 增加的原因,那么我们又该如何解决呢?
方式一:
既然是因为我们将对 vc.publicOB
的订阅加入到 控制器1 的 disposeBag
中导致无法取消订阅,那么我们就将订阅添加到 vc.disposeBag
中,让其和 控制器2 共存亡。
vc.publicOB.subscribe(onNext: { (value) in
print("\(value)")
})
.disposed(by: vc.disposeBag)
当 控制器2 pop时,控制器2执行销毁,vc.disposeBag
也会被销毁,而且内部的订阅也会被取消。
方式二:
自动取消订阅除了使用 DisposeBag
之外,还可以使用 takeUtil
操作符。
_ = vc.publicOB.takeUntil(vc.rx.deallocated)
.subscribe(onNext: { (value) in
print("\(value)")
})
takeUtil
操作符将使得订阅一直持续到控制器的 dealloc 事件产生为止。
方式三:
如果 控制器2 是被 控制器1 所持有的,那么当pop之后,控制器2 是不会被销毁的,那么又该如何处理呢?
RxSwift的内存管理可以让其自动管理订阅,其实也可以手动取消订阅,发送 completed
或者 error
事件,都可以取消订阅关系。
控制器1 代码:
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)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 页面即将消失时发送 `completed` 事件
mySubject.onCompleted()
}
上面的代码可以实现RxSwift的订阅管理,但是在使用中发现,第二次push到 控制器2 后,控制器1 无法收到信号了。
原因是,订阅在收到 completed
事件后,已经取消了订阅,无法再次接收到信号。
既然知道了原因,那么我们可以在再次push时,重新激活 publicOB
。
fileprivate var mySubject = PublishSubject<Any>()
var publicOB : Observable<Any>{
mySubject = PublishSubject<Any>()
return mySubject.asObservable()
}
这样就可以在push时,获得一个新的 publicOB
就可以再次发送接收信号了。
以上就是RxSwift内存管理的一些示例和解决方式。可能栗子举得很局限,不能代表项目中的实际情况,但是仍希望其中分析与解决方式能带给小伙伴们一起启发。若有不足之处,请评论指正。