我得到ARC编译器的以下警告:
"performSelector may cause a leak because its selector is unknown".
这是我正在做的:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
为什么我得到这个警告?我理解编译器不能检查选择器是否存在,但为什么会导致泄漏?我怎样才能改变我的代码,让我不再得到这个警告呢?
我得到ARC编译器的以下警告:
"performSelector may cause a leak because its selector is unknown".
这是我正在做的:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
为什么我得到这个警告?我理解编译器不能检查选择器是否存在,但为什么会导致泄漏?我怎样才能改变我的代码,让我不再得到这个警告呢?
当前回答
为了子孙后代着想,我决定参加竞选:)
最近,我看到越来越多的重构偏离了目标/选择器范式,而倾向于协议、块等。然而,我已经使用过几次的performSelector有一个替代物:
[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];
这些似乎是一个干净的,弧安全的,和几乎相同的替代performSelector没有太多的objc_msgSend()。
不过,我不知道iOS上是否有类似的功能。
其他回答
下面是基于上面给出的答案更新的宏。这应该允许您使用return语句来包装代码。
#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
code; \
_Pragma("clang diagnostic pop") \
SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
return [_target performSelector:_action withObject:self]
);
这段代码不涉及编译器标志或直接运行时调用:
SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];
NSInvocation允许设置多个参数,因此与performSelector不同,这将适用于任何方法。
在编译器允许覆盖警告之前,可以使用运行时作为一种变通方法。
你需要标题:
#import <objc/message.h>
然后试试下面的方法:
// For strict compilers.
((id(*)(id,SEL))objc_msgSend)(_controller, sel_getUid("someMethod"));
OR
// Old answer's code:
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
而不是:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
@c-road在这里提供了正确的问题描述链接。下面您可以看到我的示例,当performSelector导致内存泄漏时。
@interface Dummy : NSObject <NSCopying>
@end
@implementation Dummy
- (id)copyWithZone:(NSZone *)zone {
return [[Dummy alloc] init];
}
- (id)clone {
return [[Dummy alloc] init];
}
@end
void CopyDummy(Dummy *dummy) {
__unused Dummy *dummyClone = [dummy copy];
}
void CloneDummy(Dummy *dummy) {
__unused Dummy *dummyClone = [dummy clone];
}
void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
__unused Dummy *dummyClone = [dummy performSelector:copySelector];
}
void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
__unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dummy *dummy = [[Dummy alloc] init];
for (;;) { @autoreleasepool {
//CopyDummy(dummy);
//CloneDummy(dummy);
//CloneDummyWithoutLeak(dummy, @selector(clone));
CopyDummyWithLeak(dummy, @selector(copy));
[NSThread sleepForTimeInterval:1];
}}
}
return 0;
}
在我的例子中,唯一导致内存泄漏的方法是CopyDummyWithLeak。原因是ARC不知道,copySelector返回retain object。
如果你运行内存泄漏工具,你可以看到下面的图片: ...在其他任何情况下都没有内存泄漏:
这里有很多答案,但由于这个有点不同,我把几个答案结合起来,我想我应该把它放进去。我使用一个NSObject类别检查,以确保选择器返回void,也抑制编译器警告。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert
@interface NSObject (Extras)
// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;
@end
@implementation NSObject (Extras)
// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown
- (void) checkSelector:(SEL)aSelector {
// See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
Method m = class_getInstanceMethod([self class], aSelector);
char type[128];
method_getReturnType(m, type, sizeof(type));
NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
NSLog(@"%@", message);
if (type[0] != 'v') {
message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
[Debug assertTrue:FALSE withMessage:message];
}
}
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
[self checkSelector:aSelector];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
[self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop
}
- (void) performVoidReturnSelector:(SEL)aSelector {
[self checkSelector:aSelector];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector: aSelector];
#pragma clang diagnostic pop
}
@end