当我走过一些代码时,我几天前偶然发现了这个问题,
- (void)dealloc {
...
[self.postOfficeService deregister:self];
...
}
邮局服务的取消注册是异步操作,即使从界面不明显,因为没有传递给postOfficeService
的块或函数。< / p>
postOfficeService的-deregister
方法的内部实现类似于
// -deregister:(id)formerSubscriber implementation
//some trivial checks here
// deregister former subscriber
dispatch_asynch(_serialQueue, ^{
[self.subcribers removeObject:formerSubscriber];
});
...
容器self.subscribers
完美地完成了它的工作并且仅包含弱引用。即它是NSHashTable
。
只要在dealloc方法中调用了注销方法,我就会继续崩溃,而postOfficeService
试图从其异步块中的列表中删除以前的订阅者,这用于线程安全目的我猜。
在[self.subscribers removeObject:formerSubscriber]
上添加断点,可以注意到formerSubscriber
对象始终是NSZombieObject
。这就是崩溃的原因。
我知道可以在不引发此问题的情况下获得deregister
方法的线程安全性 - 我认为应该足够使用dispatch_synch来代替dispatch_asynch版本
我认为这是不应在dealloc
方法中调用异步方法的原因之一。
但问题是,即使我们处于ARC环境并且容器对象是NSHashTable
(因此它应该正常工作,我猜),如何有可能不断获取NSZombie对象?
答案 0 :(得分:2)
规则是:当调用dealloc时,一旦dealloc返回其调用者(当引用计数为0时调用release),对象就会消失,并且没有任何东西可以防止这种情况发生。
在ARC之前,你可能试图在dealloc中保留一个对象 - 没有帮助;一旦调用了dealloc,对象就会进入(如果你在dealloc中进行保留/释放,则dealloc只会被调用一次)。 ARC也会自动执行相同操作。
答案 1 :(得分:0)
使用ARC并不意味着你所有的记忆问题神奇地消失了。
发生了什么
ARC调用的[obj发布]
[obj dealloc]
free(obj) - 从这一点来说,obj是一个僵尸
GCD计划任务
正确的解决方案是使用dispatch_sync确保在解除分配后不尝试使用对象。 (小心死锁)
答案 2 :(得分:0)
-deregister
应该是同步的。+weakObjectsHashTable
或等效的选项集(NSHashTableZeroingWeakMemory
和NSPointerFunctionsObjectPersonality
)创建的。如果它不是以这种方式创建的,那么很可能你会有值指向僵尸对象。&#34;为什么我会得到僵尸&#34;最好通过使用仪器中的Zombies模板分析您的应用程序并激发所需行为来解答。
答案 3 :(得分:0)
我同意其他人的意见,你应该避免在-dealloc
方法中进行异步清理。但是,可以通过将参数设置为-deregister:
__unsafe_unretained
来解决此问题。那么该方法必须将指针纯粹视为不透明值。它不得取消引用或发送消息。很遗憾,您无法控制NSHashTable
的实施,也无法保证这一点。即使可以依赖NSHashTable
,-removeObject:
的接口也会采用隐式强对象指针,因此当从不安全的未保留指针复制指针时,ARC可能会保留指针。
您可以使用C函数API来表示哈希表(例如NSHashRemove()
),如NSHashTable
类概述中所述。