异步函数调用与无主对象的取消初始化冲突

Asynchronous function call conflicts with deinitialization of an unowned object

我有类似的代码,与异步上下文中的事件处理相关:

class A {
    var foos: Set<Fooable>
}

protocol Fooable {
    func bar()
}

class B {
    var a: A
    var foo: Foo!

    init(a: A) {
        self.a = a
    }

    func start() {
        self.foo = Foo(self)
        self.a.foos.insert(self.foo)
    }

    deinit {
        <... *>
        if self.foo != nil {
            self.a.remove(self.foo)
        }
    }


    class Foo: Fooable {
        unowned let b: B

        init(_ b: B) {
            self.b = B
        }

        func bar() { <... #> }
    }
}

我认为这应该是安全的代码:在 b 的实例消失之前,它会清除对其 foo 的所有引用,因此引用 Foo.b 永远不应该一个问题。

但是,我从 Foo.bar() 内部的 self.b 访问中得到这个错误(运行 在一些 GCD 队列上,不是主队列):

exc_breakpoint (code=exc_i386_bpt subcode=0x0)

调试器显示 self.b 完全没问题:不是零,所有值都应该是。

但是,调试器也显示,与此同时,主线程正忙于deinitializing对应的B;它在 <... *> 中暂停,即在可以从 a 中删除对 foo 的引用之前。所以对我来说 self.b 在这个时间点是一个不好的参考是有道理的。

这似乎是一个不幸的时机——但我怎样才能消除这种潜在的崩溃呢?毕竟,我无法阻止对 bar() 的异步调用!

基本上,我们在这里打破了 unowned 的先决条件:即使调试器没有显示它,Foo.b 可以 变成 nilFoo 的生命周期中。当我们声称(通过使用 unowned)它不能时,编译器相信了我们,所以我们崩溃了。

似乎有两条出路。

  1. 确保 Foo.b 是最后一个对 Foo 的相应实例拥有强引用的对象。然后,应分别删除这两个对象 "together"。在 Foo.b 被取消初始化(或之后)时,无法调用 Foo.bar()

  2. 使 Foo.b 成为弱引用,即将其声明为 weak var b: B?。这让代码变得更乱,但至少可以让它变得安全。