我有与此类似的代码,与异步上下文中的事件处理有关:
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(代码=exc_i386_bpt子代码=0x0(
调试器显示self.b
完全没问题:不是 nil,所有值都按原样。
然而,调试器也显示,与此同时,主线程正忙于取消初始化相应的B
;它在<... *>
中暂停,即在从a
中删除对foo
的引用之前。因此,在我看来,self.b
在这个时间点将是一个糟糕的参考是有道理的。
这似乎是不幸的时机 - 但我如何消除这种崩溃的可能性?毕竟,我无法阻止对bar()
的异步调用发生!
基本上,我们在这里打破了unowned
的前提条件:即使调试器不显示它,Foo.b
也可能在Foo
的生命周期内变得nil
。当我们声称(通过使用unowned
(它不能时,编译器相信了我们,所以我们崩溃了。
似乎有两条出路。
-
确保
Foo.b
是最后一个包含对相应实例的强引用的对象Foo
。然后,这两个对象应该"一起"删除,因为在取消初始化Foo.b
时(或之后(不可能发生对Foo.bar()
的调用。 -
将
Foo.b
作为弱引用,即将其声明为weak var b: B?
。这使得代码更加混乱,但至少它可以变得安全。