为啥在另一个线程上发生 dealloc 时尝试创建对 self 的弱引用时会崩溃?
Posted
技术标签:
【中文标题】为啥在另一个线程上发生 dealloc 时尝试创建对 self 的弱引用时会崩溃?【英文标题】:Why am I crashing when trying to create a weak reference to self while dealloc is happening on another thread?为什么在另一个线程上发生 dealloc 时尝试创建对 self 的弱引用时会崩溃? 【发布时间】:2017-11-29 21:14:48 【问题描述】:背景
我们的应用有一个尝试实现Receptionist Pattern for KVO observation 的类。整个应用程序中的其他类(例如视图控制器)创建这个 Receptionist 类的实例以充当 KVO 观察者。每个 Receptionist 实例保留一份由所有者提供的块的副本,当 KVO 通知到达时,Receptionist 实例将在正确的操作队列上调用该块。
接待员的 dealloc 方法调用 KVO removeObserver 方法。 Owner 将 Receptionist 实例保留为强引用字段,因此当 Owner 被释放时,Receptionist 将在释放过程中将自己作为观察者移除。
崩溃
当一个线程上的 Receptionist 实例收到 KVO 通知,而另一个线程上正在执行同一实例的 dealloc 时,我们看到了来自崩溃领域的报告。接待员对 observeValueForKeyPath:ofObject:change:context: 的实现在这一行崩溃了:
__weak typeof(self) weakSelf = self;
崩溃报告中的堆栈跟踪显示这是对 objc_initWeak 的调用,它调用了weak_register_no_lock,它又调用了 _objc_fatal。
这个特定的接待员正在观察其键的对象永远不会被释放。所有者也没有被解除分配; Owner 正在用其他实例替换这个 Receptionist 实例。
混乱
我可以理解,创建对已经被释放的对象的弱引用是没有用的,但我希望weakSelf 接收一个 nil 值,而不是导致崩溃。
documentation for objc_initWeak 明确提到如果需要引用的参数已开始解除分配,则将目标设置为 null。这听起来像是期望的行为,但我认为这不是我所看到的。我并不热衷于用对 objc_initWeak 的显式调用来替换该行,因为我怀疑我是否能正确管理释放。
在请求对 self 的弱引用之前,是否真的是接待员的责任注意到它自己的释放正在进行中?我假设在 NSObject 的解除分配开始和调用该对象的 dealloc 方法之间存在一些窗口,因此从 dealloc 方法发出的对象内的信号听起来很不稳定。
感谢您的阅读!
PS:在阅读了 Ken Thomases 提出的问题后进行了大量编辑。
【问题讨论】:
因此,每个 Receptionist 实例都包含对其观察对象的强引用,此引用在初始化期间分配而不会分派到另一个线程,“addObserver:”也在初始化期间调用而不分派到另一个线程,以及“removeObserver:”在释放期间调用? @SergeyAlpeev 是的。我确认接待员对被观察对象的引用是强的,并且在接待员的初始化期间被填充。 addObserver: 也发生在接待员的初始化期间。 removeObserver:在dealloc中调用。一种以前没有注意到的可疑气味:所有属性都使用 self.x = x 而不是 _x = x 初始化(在 addObserver 之前)。似乎仍然不太可能是罪魁祸首。 【参考方案1】:这与弱引用的创建无关。您引用的这行代码只能在强烈引用 self
的上下文中运行。
想一想:您看到的崩溃可能发生在您的 observeValueForKeyPath:ofObject:change:context:
实现中的那一行,但是,由于在释放和调用该方法之间显然存在竞争,释放也可能在调度期间发生该方法调用(或其他点)。你很容易受到不同的崩溃。因此,对方法的实现没有任何更改可能会解决问题,因为问题可能在您的方法被调用之前就已经显现。
如果您要调用该对象的方法,那么您有责任保持对该对象的强引用。或者,从另一个角度来看,避免在对象指针上调用您不确定是否会在调用期间存在的方法(因为您持有强引用或其他一些 API 保证)。
使用 KVO,您需要在释放最后一个强引用之前移除观察者。
【讨论】:
你关于方法其余部分的观点是有效的,但只要 self 为 nil,它就不会崩溃。我们有接待员的一半原因是观察者的移除作为 ARC 释放的一部分自动处理。对于业主需要警告接待员它即将被取消分配的想法,我真的很难接受。 Apple docs 明确表示“一个典型的模式是......在解除分配期间取消注册(通常在 dealloc 中)”。 什么情况下self
可以为nil?你一开始是怎么接触到这种方法的?观察者移除不会自动处理。您引用的相同文档说“观察者在释放时不会自动删除自己。被观察对象继续发送通知,忽略观察者的状态。但是,像任何其他消息一样,更改通知发送到已释放对象,触发内存访问异常。因此,您要确保观察者在从内存中消失之前将自己移除。"
我不知道您认为哪个对象是“所有者”。无论添加什么对象,观察者通常负责 a) 保持对观察者的强引用,并且可能是被观察对象; b) 在释放对它的强引用之前移除观察者。
感谢您的反馈!我已经编辑了这个问题,希望能回答你的问题,但为了便于阅读,我会在这里重复一遍。 Receptionist 是一个在我们的应用程序中使用了几十个地方来简化 KVO 的类。每个所有者为所有者希望观察的每个变量创建一个接待员实例。每个 Receptionist 都作为 Owner 的强引用保存,因此如果 Owner 被解除分配,Receptionist 将被自动解除分配。接待员的 dealloc 方法调用 removeObserver。问题不在于接待员并没有将自己作为观察员。这发生在 dealloc 的末尾。以上是关于为啥在另一个线程上发生 dealloc 时尝试创建对 self 的弱引用时会崩溃?的主要内容,如果未能解决你的问题,请参考以下文章