Natsu

博客

ようこそいらっしゃいました。


在ARC下调用performSelector方法导致的内存泄露问题

当我们使用performSelector方法的时候,如:

[object performSelector:@selector(someMethod)];

[object performSelector:NSSelectorFromString(@"someMethod")];

在ARC情况下,可能会导致编译器报如下警告:

"performSelector may cause a leak because its selector is unknown".

原因

由于这段代码是动态的调用someMethod方法,编译器并不了解这个方法的返回值,因此编译器也无法用ARC的内存管理机制来进行管理。ARC对此的做法就是不添加释放操作,因此当调用的方法会使调用对象引用计数增加时,将会导致内存泄露。

解决方法

1.利用C函数指针方法调用函数

你可以将上面的方法调用改写成如下方式:

if (!object) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [object methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(object, selector);

或者,你可以写的更简单一点:

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[object methodForSelector:selector])(object, selector);

如果方法有返回值或者需要传入参数的话,你需要做一些改变,例如:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [object methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = object ? func(object, selector, someRect, someView) : CGRectZero;

2.让编译器忽略这些警告

通过使用clang diagnostic ignored来使编译器忽略指定的警告,如果你有多处报这个警告的话,你可以定义如下的宏来使代码更简洁。

#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
    Stuff; \
    _Pragma("clang diagnostic pop") \
} while (0)

在使用时,你只需要这样做:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

如果你需要方法的返回值的话,你可以这样做:

id result;
SuppressPerformSelectorLeakWarning(
	result = [_target performSelector:_action withObject:self]
);

总结

当你无法确定调用的方法是否会造成内存泄露时,最好不要这样做。例如当你在调用类似initSomethingnewSomethingsomethingCopy等会使引用计数增加方法时,是可能会导致内存泄露。例如,如下的调用回导致内存泄漏:

NSArray *array = @[@"a",@"b"];
[array performSelector:NSSelectorFromString(@"copy")];

在Xcode7.3版本中,使用如下写法:

NSArray *array = @[@"a",@"b"];
[array performSelector:@selector(copy)];

此时编译器会直接报错:

PerformSelector names a selector which retains the object

这时无论使用上述哪种方法都无法解决内存泄漏。在MRC模式下,加上array = nil;是可以解决内存泄漏的,但是在ARC模式下不行。在网上也看了好多资料,试了多种方法,但这个问题还是没有解决。所以,在ARC模式下,尽量不要使用performSelector方法来调用initSomethingnewSomethingsomethingCopy等会使引用计数增加方法。

参考资料:http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown