为什么在做printOnSelf("onFire")
的时候会得到attempted read of an already deallocated reference分?
在调用subject!.startEmitter()
之后,应该有两个对TimerInvalidator
的引用:第一个来自Controller
AS let invalidator = TimerInvalidator()
,第二个来自Observable.create
闭包,它捕获局部变量invalidator
并因此持有对它的引用.
当Controller
被释放时,对TimerInvalidator
的第一个引用应该丢失,而第二个引用仍然被捕获在Observable.create
闭包内.但由于Controller
已被释放,DisposeBag
也应同时被释放并处理所创建的可观测序列.这应该是Observable.create
闭包的deallocate,因此删除了对TimerInvalidator
的第二个引用.这应该意味着TimerInvalidator
也应该被释放并使定时器无效.
但在现实中,定时器在Controller
被释放很久之后才触发,导致在访问self
时出错.我遗漏了什么?
当然,不在Observable.create
中访问self
会更有意义,而是在Create方法之后在.do(onNext:)
闭包中访问.或者,可以通过在通过更改以下内容来处理序列时使计时器无效来修复该问题:
return Disposables.create {
print("Emitter.start.create.dispose")
invalidator.timer?.invalidate()
}
或者,可以通过将TimerInvalidator
捕获为unowned
来修复它,方法是更改:
return Observable.create { [unowned self, unowned invalidator] event in
...
但我感兴趣的是为什么这种方法不好.也是为什么这些修复会奏效.
下面的代码显然只是一个最小的示例,我将其作为一个包运行.
main.swift个
import Foundation
import RxSwift
import RxCocoa
class TimerInvalidator {
var timer: Timer?
deinit {
print("TimerInvalidator.deinit")
timer?.invalidate()
}
}
class Controller {
let db = DisposeBag()
let invalidator = TimerInvalidator()
let emitter = Emitter()
func startEmitter() {
print("Controller.startEmitter")
emitter.createSignal(with: invalidator)
.emit() // Do stuff
.disposed(by: db)
}
deinit {
print("Controller.deinit")
}
}
class Emitter {
func printOnSelf(_ string: String) {
// Represents some operation on self
print("\(Self.self): " + string)
}
func createSignal(with invalidator: TimerInvalidator) -> Signal<String> {
return Observable.create { [unowned self] event in
printOnSelf("onCreated")
invalidator.timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { [unowned self] timer in
printOnSelf("onFire")
event.onNext("fire")
event.onCompleted()
}
return Disposables.create {
print("Emitter.start.create.dispose")
}
}
.asSignal(onErrorJustReturn: "error")
}
deinit {
print("Emitter.deinit")
}
}
var subject: Controller? = Controller()
subject!.startEmitter()
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
subject = nil
}
RunLoop.current.run(mode: .default, before: Date() + 10)
Package.swift(与问题没有直接关系,只是为了方便起见)
import PackageDescription
let package = Package(
name: "StackOverflow",
products: [
.executable(name: "StackOverflow", targets: ["StackOverflow"])
],
dependencies: [
.package(url: "https://github.com/ReactiveX/RxSwift", exact: "6.5.0")
],
targets: [
.target(
name: "StackOverflow",
dependencies: ["RxSwift", .product(name: "RxCocoa", package: "RxSwift")]),
]
)