目标行动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正确数量的retainrelease的工作,因为它根本不知道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);