目标行动devise模式在ARC下变得糟糕吗?
多年来,我一直在追踪一个名为Target-Action的伟大模式,如下所示:
一个对象在时间到来时调用指定的目标对象上的指定select器。 这在很多不同的情况下非常有用,你需要一个简单的callback方法。
这是一个例子:
- (void)itemLoaded { [specifiedReceiver performSelector:specifiedSelector]; }
在ARC之下,现在我们发现这样做突然变得危险了。
Xcode抛出一个警告,如下所示:
PerformSelector可能会导致泄漏,因为它的select器是未知的
当然,select器是未知的,因为作为Target-Actiondevise模式的一部分,您可以指定任何您想要的select器,以便在发生有趣的事情时接听电话。
这个警告最让我感到困惑的是它说可能会有潜在的内存泄漏。 根据我的理解,ARC不会弯曲内存pipe理规则,而只是在正确的位置自动插入保留/释放/自动释放消息。
另外需要注意的是:-performSelector:有一个id
返回值。 如果该方法返回+1保留计数对象,则ARC通过应用命名约定来分析方法签名。 在这种情况下,ARC不知道select器是否是-newFooBar
工厂,或者只是调用一个不可靠的工作者方法(反正Target-Action几乎总是这样)。 其实ARC应该已经认识到我不期望有一个返回值,因此忘记了任何潜在的+1保留计数的返回值。 从这个angular度来看,我可以看到ARC从哪里来,但仍然存在着太多的不确定性,这在实践中意味着什么。
那现在是否意味着在ARC之下会出现什么问题呢? 我不明白这是如何产生内存泄漏的。 有人可以举例说明这样做的危险性,以及在这种情况下如何产生泄漏?
我真的从互联网上下了地狱,但没有find任何网站解释为什么 。
performSelector
的问题是,ARC不知道select器将执行什么。 考虑以下:
id anotherObject1 = [someObject performSelector:@selector(copy)]; id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];
现在,ARC如何知道第一个返回保留计数为1的对象,而第二个返回的是一个自动释放的对象? (我只是在这里定义一个名为giveMeAnotherNonRetainedObject
的方法,它返回一些自动发布的东西)。 如果没有添加任何发行版,那么anotherObject1
会在这里泄漏。
显然在我的例子中,要执行的select器实际上是已知的,但想象一下,它们是在运行时select的。 ARC确实无法完成在这里input正确数量的retain
或release
的工作,因为它根本不知道select器将要做什么。 你说得对,ARC并没有弯曲任何规则,只是为你添加了正确的内存pipe理调用,但这正是它不能做到的事情。
你是对的,你忽略了回报价值的事实意味着它会好起来的,但是一般来说ARC只是挑剔和警告。 但我想这就是为什么这是一个警告,而不是一个错误。
编辑:
如果你确定你的代码是可以的,你可以隐藏这样的警告:
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [specifiedReceiver performSelector:specifiedSelector]; #pragma clang diagnostic pop
警告应该像这样读取:
PerformSelector可能会导致泄漏,因为它的select器是未知的。 ARC不知道返回的id是否有+1保留计数,因此不能正确pipe理返回对象的内存。
不幸的是,这只是第一句话。
现在解决scheme:
如果您从-performSelector方法接收到返回值,则无法对代码中的警告执行任何操作(忽略该警告除外)。
NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor];
你最好的select是这样的:
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor]; #pragma clang diagnostic pop
在我最初的问题中,我完全忽略了返回值。 ARC应该足够聪明,看看我不关心返回的id,因此匿名select器几乎可以保证不是工厂,便捷构造器或任何其他的东西。 不幸的是ARC不是,所以同样的规则适用。 忽略警告。
也可以通过在项目构build设置中的“其他警告标志”下设置-Wno-arc-performSelector-leaks编译器标志来完成整个项目。
或者,您可以在每个文件的基础上抑制警告,当您在目标>“生成阶段”>“编译源”下的所需文件旁边右侧添加该标志时。
所有这三个解决scheme是非常混乱恕我直言,所以我希望有人提出一个更好的。
如上所述,您会收到警告,因为编译器不知道在哪里(或如果)放置了performSelector的保留/释放:返回值。
但是请注意,如果使用[someObject performSelector:@selector(selectorName)]
,它将不会生成警告(至less在Xcode 4.5中使用llvm 4.1),因为确切的select器很容易确定(你明确地设置它),这就是为什么编译器是能够把保留/释放在正确的地方。
这就是为什么只有在使用SEL指针传递select符时才会发出警告,因为在这种情况下,编译器无法确定所有的操作。 所以使用以下
SEL s = nil; if(condition1) SEL = @selector(sel1) else SEL = @selector(sel2) [self performSelector:s];
会产生警告。 但重构它是:
if(condition1) [self performSelector:@selector(sel1)] else [self performSelector:@selector(sel2)]
不会产生任何警告
ARC会抛出警告,因为它不能保证select器不会创build一个它不知道的对象。 理论上你可以从ARC无法处理的方法中获得一些东西:
id objectA = [someObject performSelector:@selector(createObjectA)];
也许有一天它可以,但现在它不能。 (注意,如果它确实知道对象(它不是一个ID)它不会抛出这个警告)。
如果你试图简单地执行一个方法而不从它接收对象,我推荐使用objc_msgSend。 但是你必须在你的课堂上join:
#include <objc/message.h> objc_msgSend(someObject, action);