Solution
编译器对此发出警告是有原因的。简单地忽略此警告的情况很少见,而且很容易解决。就是这样:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
或者更简洁(虽然很难阅读并且没有守卫):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
解释
What's going on here is you're asking the controller for the C function pointer for the method corresponding to the controller. All NSObject
s respond to methodForSelector:
, but you can also use class_getMethodImplementation
in the Objective-C runtime (useful if you only have a protocol reference, like id<SomeProto>
). These function pointers are called IMP
s, and are simple typedef
ed function pointers (id (*IMP)(id, SEL, ...)
)1. This may be close to the actual method signature of the method, but will not always match exactly.
一旦你拥有了IMP
,您需要将其转换为函数指针,其中包含 ARC 所需的所有详细信息(包括两个隐式隐藏参数self
and _cmd
每个 Objective-C 方法调用)。这是在第三行处理的((void *)
右侧只是告诉编译器您知道自己在做什么,并且不会生成警告,因为指针类型不匹配)。
Finally, you call the function pointer2.
复杂的例子
当选择器接受参数或返回值时,您必须进行一些更改:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
警告的理由
The reason for this warning is that with ARC, the runtime needs to know what to do with the result of the method you're calling. The result could be anything: void
, int
, char
, NSString *
, id
, etc. ARC normally gets this information from the header of the object type you're working with.3
There are really only 4 things that ARC would consider for the return value:4
- 忽略非对象类型(
void
, int
, etc)
- 保留对象值,然后在不再使用时释放(标准假设)
- 当不再使用时释放新的对象值(方法在
init
/ copy
家庭或归因于ns_returns_retained
)
- 什么都不做并假设返回的对象值在本地范围内有效(直到最内部的释放池被耗尽,归因于
ns_returns_autoreleased
)
致电给methodForSelector:
假设它调用的方法的返回值是一个对象,但不保留/释放它。因此,如果您的对象应该像上面#3 中那样被释放(即您调用的方法返回一个新对象),那么您最终可能会造成泄漏。
对于选择器,您尝试调用该返回void
或其他非对象,您可以启用编译器功能来忽略警告,但这可能很危险。我见过 Clang 如何处理未分配给局部变量的返回值的一些迭代。启用 ARC 后,没有理由不能保留和释放从methodForSelector:
即使您不想使用它。从编译器的角度来看,它毕竟是一个对象。这意味着如果您调用的方法,someMethod
,返回一个非对象(包括void
),您最终可能会保留/释放垃圾指针值并崩溃。
附加参数
一个考虑因素是,这与以下情况会发生相同的警告:performSelector:withObject:
如果不声明该方法如何使用参数,您可能会遇到类似的问题。 ARC 允许声明消耗的参数,如果该方法消耗该参数,您最终可能会向僵尸发送消息并崩溃。有多种方法可以通过桥接转换来解决这个问题,但实际上最好简单地使用IMP
和上面的函数指针方法。由于消耗的参数很少成为问题,因此这种情况不太可能出现。
静态选择器
有趣的是,编译器不会抱怨静态声明的选择器:
[_controller performSelector:@selector(someMethod)];
这样做的原因是因为编译器实际上能够在编译期间记录有关选择器和对象的所有信息。它不需要对任何事情做出任何假设。 (我一年前通过查看来源检查了这一点,但现在没有参考资料。)
抑制
在试图考虑有必要抑制此警告和良好的代码设计的情况时,我一片空白。如果有人有过需要消除此警告的经验(并且上述方法不能正确处理问题),请分享。
More
可以建立一个NSMethodInvocation
也可以处理这个问题,但是这样做需要更多的输入并且速度也较慢,所以没有理由这样做。
History
当。。。的时候performSelector:
方法族首先添加到 Objective-C 中,ARC 并不存在。在创建 ARC 时,Apple 决定应该为这些方法生成警告,以指导开发人员使用其他方法来明确定义通过命名选择器发送任意消息时应如何处理内存。在 Objective-C 中,开发人员可以通过对原始函数指针使用 C 风格转换来实现此目的。
随着 Swift 的推出,Apple已记录 the performSelector:
一系列方法被视为“本质上不安全”,并且它们不适用于 Swift。
随着时间的推移,我们看到了这样的进展:
- Objective-C 的早期版本允许
performSelector:
(手动内存管理)
- Objective-C with ARC 警告使用
performSelector:
- 斯威夫特无权访问
performSelector:
并将这些方法记录为“本质上不安全”
然而,基于命名选择器发送消息的想法并不是“本质上不安全”的功能。这个想法已经在 Objective-C 以及许多其他编程语言中成功使用了很长时间。
1 All Objective-C methods have two hidden arguments, self
and _cmd
that are implicitly added when you call a method.
2 Calling a NULL
function is not safe in C. The guard used to check for the presence of the controller ensures that we have an object. We therefore know we'll get an IMP
from methodForSelector:
(though it may be _objc_msgForward
, entry into the message forwarding system). Basically, with the guard in place, we know we have a function to call.
3 Actually, it's possible for it to get the wrong info if declare you objects as id
and you're not importing all headers. You could end up with crashes in code that the compiler thinks is fine. This is very rare, but could happen. Usually you'll just get a warning that it doesn't know which of two method signatures to choose from.
4 See the ARC reference on retained return values and unretained return values for more details.