为什么在做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")]),
    ]
)

推荐答案

可观察的合同保证当调用处置包的deinit时将调用订阅的dispose().它不能保证与可观测对象相关的内存将在什么时候被清除.这取决于底层的iOS VM,如果你查看应用程序崩溃时TimerInvalidator的内存图,它仍然保持着可观察到的状态.

你可能注意到,在应用程序崩溃的时候,你的TimerInvalidator%S代码还没有被调用.但是,如果您删除有问题的行并让应用程序继续运行,则deinit does get called.

请记住,无论底层内存模型是什么,Rx系统都被设计为具有可确定的行为.这就是为什么create函数要求您返回正确清理资源的Disposable.如果做不到这一点,您就违反了约定,这使得库的行为没有被定义.

你说过:

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

事实上,这是解决这个问题的唯一办法(就像遵守合同一样).

Swift相关问答推荐

为表单部分赋予背景 colored颜色 /渐变

查找数组中 ** 元素 ** 的属性的最小值和最大值

SwiftUI-如何使用剩余时间制作倒计时计时器

在一行语句中比较Date.now的相等性是否安全

为什么这个快速代码不显示文本字段?

当变量首次用于其自己的初始化时,如何清除变量...在初始化之前使用错误?

Swift Random Float Fatal Error:在无限范围内没有均匀分布

一组可散列的自定义元素插入相等的元素

当使用 CGFloat 而不是数字文字时,为什么 node 的位置会发生变化?

Swift - 给定一个像 30 的 Int 如何将范围数乘以 7?

如何在填充上添加 if else 语句? SwiftUI

在视图中设置所有变量

符合协议要求委托变量在 ios13 中可用

如何在 iOS Swift 中进行多线程、并发或并行?

FCM 后台通知在 iOS 中不起作用

将强制向下转换视为可选将永远不会产生零

如何从一个可观察的数组创建一个可观察的数组?

如何从 UITableViewCell 类中引用 UITableViewController?

如何使用 Swift 枚举作为字典键? (符合 Equatable)

如何在swift中的每N个字符处为字符串添加分隔符?