根据非垃圾收集世界中的内存管理规则,不应该在委托中保留调用对象。场景如下:
我有一个继承自UITableViewController并包含搜索栏的类。我在辅助线程中运行昂贵的搜索操作。这一切都是通过NSOperationQueue和子类NSOperation实例完成的。我将控制器作为代表传递给NSOperation。
当应用程序崩溃时会出现边缘情况,因为一旦从UITableViewController中选择了一个项目,我就会忽略它,因此它的保留计数变为0并且在其上调用dealloc。委托人没有及时发送消息,因为结果是在dealloc发生的同时传递的。
我应该以不同的方式设计吗?我是否应该从代理处调用我的控制器上的retain以确保它存在,直到NSOperation本身被取消分配?这会导致内存泄漏吗?现在,如果我在控制器上放置一个保留,崩溃就会消失。我不想泄漏内存,并且需要了解是否存在保留委托有意义的情况。
回顾一下。
UITableViewController创建一个嵌入队列的NSOperationQueue和NSOperation。 UITableViewController将自身作为NSOperation的委托传递。 NSOperation在UITableViewController准备就绪时调用它。如果我保留了UITableViewController,我保证它就在那里,但我不确定我是否在泄漏内存。如果我只使用assign属性,则会出现边缘情况,其中UITableViewController被dealloc'd,objc_msgSend()被调用在内存中不存在的对象上,并且即将发生崩溃。
答案 0 :(得分:3)
在一般情况下,委托拥有它自己设置为委托的对象,或者至少保留对它们的直接或间接引用。在dealloc方法中,委托应该释放它作为委托的所有对象,以防止将来的回调,如NSTimer无效,或者清除那些可能持久存在的对象的委托成员。
虽然只是约会阻止保留代表,但它是基于良好设计的惯例。在你的情况下,自从委托被处理以后,结果是否会被丢弃?
您可以通过不设置非原子标志并合成getter和setter来使NSOperation的delegate属性成为原子。或者,您可以在使用委托成员之前使用performSelectorOnMainThread。
总结一下,通常有一个比保留代表更好的解决方案。
答案 1 :(得分:2)
我真的很想知道“不保留你的委托”规则是否仍然专门适用于多线程对象/委托关系,尤其是你正在撰写的关系。我找到了解决这个问题的方法,因为我处于完全相同的情况:我正在运行一个有限但不可预测的长度异步网络操作(它将在NSOperation中完成并自动终止“很快”)并使用委托存储在NSOperation中以将操作完成通知回原始请求者对象。
在这种情况下保留代表对我来说非常有意义。操作最终将完成,它将在委托上调用“done”方法,然后释放委托并自行终止。如果调用对象在NSOperation运行时已经被释放,那么它只会停留一段时间直到操作完成。
请注意,将委托属性原子标记为drawnonward建议本身不足以以防止竞争条件!将属性设置为仅原子意味着属性的读者或编写者将一次写入或读取整个整个值,别无其他。以下序列将导致崩溃:
(NSOperation线程):NSOperation已完成并准备通知委托对象。代码以原子方式读取准备调用其“完成”方法的委托属性的值。
---->线程切换到主
(主线程)请求对象被dealloc'd,并且在-(void)dealloc
(原子地!)中将NSOperation的委托属性设置为nil,dealloc结束,对象现在消失了
- - - - >线程切换到NSOperation线程
(NSOperation线程)调用[委托allDone]并且程序崩溃(如果你很幸运)因为委托对象消失了。如果你很幸运。在此期间可能还有其他东西被分配了,现在你有了不可预测的腐败,这很有趣!
这里的关键是保留周期临时本质上 - NSOperation将自行完成并清理所有内容。它不像UITextField和UIViewController,它们保持对彼此的保留引用,永远不会消失,从而泄漏内存。
在我看来,在异步,有限,自终止操作的情况下保留委托是最干净,最强大的实现。
编辑:
如果绝对不能保留委托,因为它会导致内存问题,那么委托对象和操作对象必须都使用显式锁定机制来同步对存储在操作中的委托指针的访问。 “atomic”属性 not 提供一个线程安全协议来标记委托指针nil。
但我认为这会使真的变得复杂且充满竞争条件。我认为至少你必须在委托对象的dealloc中运行一个锁定协议,以确保操作的委托指针安全地设置为nil,这样任意线程交错在任何情况下都不能调用dealloc'd委托对象。
斯拉夫人遵守规则有时会使事情变得比他们需要的更复杂。最好了解规则是什么,为什么它们在那里,所以你知道它什么时候有意义(比如我相信它在这个非常特殊的场景中)不遵循它们,以及这样做的优点/缺点是什么。
答案 2 :(得分:1)
我的处理方式略有不同,我不会留下代表。
如果我需要丢失视图,我会取消NSOperation。我相信它的好设计,如果线程的返回值无处可去,那么线程就应该停止。
我也看到了无法停止线程的边缘情况。在这种情况下,为了确保这一点,我没有得到保留的委托,并且在进行回调之前检查委托是否为零。
当一个物体不再需要时,甚至是暂时的物体,特别是在iPhone上咀嚼记忆,并且在我看来,这是一个糟糕的设计。
根据@ Bogatyr的回答,在调用[delegate allDone]之前对nil进行简单的检查就更清晰了。
答案 3 :(得分:0)
您可以在UITableViewController
的开头保留NSOperation
并在结尾处将其释放。
或者,您可以在发布后将其设置为nil
,以便来自NSOperation
的悬空呼叫不会导致您的程序崩溃。
根据{{1}}的执行方式,您也可以自动发布NSOperation
而不是释放它,因此UITableViewController
仍然可以使用它。
然而,所有这些事情只能治愈症状,而不是疾病。 正确的方法由drawonward概述。