Observable.create 捕获行为

Observable.create capture behaviour

提问人:MrKew 提问时间:8/5/2023 最后编辑:MrKew 更新时间:8/5/2023 访问量:47

问:

为什么这会导致在执行此操作时尝试读取已解除分配的引用printOnSelf("onFire")

调用后,应该有两个对 的引用:第一个来自 as,第二个来自捕获局部变量并因此包含对它的引用的闭包。subject!.startEmitter()TimerInvalidatorControllerlet invalidator = TimerInvalidator()Observable.createinvalidator

当被释放时,对 的第一个引用应该丢失,而第二个引用仍然在闭包中捕获。但是,由于 已解除分配,因此 也应该同时解除分配并处理创建的可观察序列。这应该解除对闭包的分配,从而删除 .这应该意味着也应该被释放并使计时器无效。ControllerTimerInvalidator Observable.createControllerDisposeBagObservable.createTimerInvalidatorTimerInvalidator

但实际上,计时器在释放后很久才会触发,从而导致访问时出错。我错过了什么?Controllerself


当然,不在 内部访问,而是在 create 方法之后的闭包内访问会更有意义。或者,当序列被处理时,可以通过更改来修复它:selfObservable.create.do(onNext:)

return Disposables.create {
    print("Emitter.start.create.dispose")
    invalidator.timer?.invalidate()
}

或者可以通过更改以下内容来捕获它:TimerInvalidatorunowned

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)

包.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")]),
    ]
)
参考 RX-SWIFT

评论


答:

2赞 Daniel T. 8/5/2023 #1

可观察的合约保证在调用处置袋时调用订阅。它不能保证与 Observable 关联的内存何时被清理。这取决于底层 iOS VM,如果你查看应用崩溃时的内存图,它仍然保留 Observable。dispose()deinitTimerInvalidator

您可能注意到,在应用程序崩溃时,您的 deinit 尚未被调用。但是,如果您删除有问题的行并让应用程序继续,则确实会调用 deinit。TimerInvalidator

请记住,无论底层内存模型是什么,Rx 系统都设计为具有可确定的行为。这就是为什么该函数要求您返回正确清理资源的原因。如果不这样做,你就违反了合同,使库的行为没有定义。createDisposable

你说:

...它可以通过在处理序列时使计时器失效来修复......

事实上,这是解决它的唯一有效方法(如遵循合同)。