Edu*_*coz 1251 memory-leaks objective-c ios automatic-ref-counting
ARC编译器收到以下警告:
"performSelector may cause a leak because its selector is unknown".
Run Code Online (Sandbox Code Playgroud)
这是我正在做的事情:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Run Code Online (Sandbox Code Playgroud)
为什么我会收到此警告?我理解编译器无法检查选择器是否存在,但为什么会导致泄漏?我怎样才能更改我的代码,以便我不再收到此警告?
wby*_*ung 1206
由于某种原因,编译器会对此发出警告.这个警告应该被忽略,这很容易解决.这是如何做:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Run Code Online (Sandbox Code Playgroud)
或者更简洁(虽然难以阅读且没有警卫):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Run Code Online (Sandbox Code Playgroud)
这里发生的是你要求控制器输入与控制器对应的方法的C函数指针.所有NSObject的响应methodForSelector:,但您也可以class_getMethodImplementation在Objective-C运行时使用(如果您只有协议引用,则很有用id<SomeProto>).这些函数指针称为IMPs,是简单的typedefed函数指针(id (*IMP)(id, SEL, ...))1.这可能接近方法的实际方法签名,但并不总是完全匹配.
有了之后IMP,需要将它转换为包含ARC所需的所有细节的函数指针(包括两个隐式隐藏参数self和_cmd每个Objective-C方法调用).这是在第三行处理的((void *)右侧只是告诉编译器你知道你在做什么而不是因为指针类型不匹配而不生成警告).
最后,调用函数指针2.
当选择器接受参数或返回值时,您将不得不稍微改变一下:
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;
Run Code Online (Sandbox Code Playgroud)
这种警告的原因是,使用ARC,运行时需要知道如何处理您正在调用的方法的结果.其结果可能是什么:void,int,char,NSString *,id,等ARC通常会从您正在使用的对象类型的报头信息.3
ARC确实只有4件事可以考虑返回值:4
void,int等)init/ copyfamily中的方法或属于的方法ns_returns_retained)ns_returns_autoreleased)调用methodForSelector:假定它调用的方法的返回值是一个对象,但不保留/释放它.因此,如果您的对象应该像上面的#3那样被释放(也就是说,您正在调用的方法返回一个新对象),那么最终可能会创建泄漏.
对于您尝试调用该返回void或其他非对象的选择器,您可以启用编译器功能来忽略该警告,但这可能很危险.我已经看到Clang经历了一些如何处理未分配给局部变量的返回值的迭代.没有理由在启用ARC的情况下,methodForSelector:即使您不想使用它,它也无法保留和释放从中返回的对象值.从编译器的角度来看,它毕竟是一个对象.这意味着如果您正在调用的方法someMethod返回非对象(包括void),则最终可能会保留/释放垃圾指针值并崩溃.
一个考虑因素是这将发生相同的警告performSelector:withObject:,你可能会遇到类似的问题,而不是声明该方法如何使用参数.ARC允许声明消耗的参数,如果方法使用参数,您最终可能会向僵尸发送消息并崩溃.有一些方法可以解决桥接转换问题,但实际上最好只使用IMP上面的和函数指针方法.由于消耗的参数很少成为问题,因此不太可能出现.
有趣的是,编译器不会抱怨静态声明的选择器:
[_controller performSelector:@selector(someMethod)];
Run Code Online (Sandbox Code Playgroud)
这是因为编译器实际上能够在编译期间记录有关选择器和对象的所有信息.它不需要对任何事情做任何假设.(我之前通过查看来源检查了一年,但现在没有参考.)
在试图考虑这种警告的抑制和良好的代码设计的情况时,我会发现空白.有人请分享,如果他们有经验,需要沉默这个警告(以上情况不妥善处理).
也可以建立一个NSMethodInvocation处理这个,但这样做需要更多的打字,也更慢,所以没有理由这样做.
当performSelector:首次将方法族添加到Objective-C时,ARC不存在.在创建ARC时,Apple决定应为这些方法生成警告,以指导开发人员使用其他方法明确定义在通过命名选择器发送任意消息时应如何处理内存.在Objective-C中,开发人员可以通过在原始函数指针上使用C样式转换来实现此目的.
随着Swift的引入,Apple 已将这一performSelector:系列方法记录为"本质上不安全",并且它们不适用于Swift.
随着时间的推移,我们看到了这种进展:
performSelector:(手动内存管理)performSelector:performSelector:并将这些方法记录为"本质上不安全"然而,基于命名选择器发送消息的想法不是"固有的不安全"特征.这个想法已经在Objective-C以及许多其他编程语言中成功使用了很长时间.
1所有Objective-C方法都有两个隐藏的参数,self并且_cmd在调用方法时会隐式添加这些参数.
2NULL在C中调用函数是不安全的.用于检查控制器是否存在的防护确保我们有一个对象.因此,我们知道我们将获得一个IMP来自methodForSelector:(虽然可能是_objc_msgForward,进入消息转发系统).基本上,在守卫到位的情况下,我们知道我们有一个可以打电话的功能.
3实际上,如果将对象声明为id并且您没有导入所有标题,则可能会获取错误的信息.您最终可能会遇到编译器认为正常的代码崩溃.这种情况非常罕见,但可能会发生.通常,您只会收到警告,表示它不知道可以选择哪两种方法签名.
4有关更多详细信息,请参阅有关保留返回值和未返回返回值的ARC参考.
Sco*_*son 1180
在Xcode 4.2中的LLVM 3.0编译器中,您可以按如下方式禁止警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop
Run Code Online (Sandbox Code Playgroud)
如果您在多个地方收到错误,并且想要使用C宏系统来隐藏编译指示,则可以定义一个宏以便更容易地抑制警告:
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
Run Code Online (Sandbox Code Playgroud)
您可以像这样使用宏:
SuppressPerformSelectorLeakWarning(
[_target performSelector:_action withObject:self]
);
Run Code Online (Sandbox Code Playgroud)
如果需要执行消息的结果,可以执行以下操作:
id result;
SuppressPerformSelectorLeakWarning(
result = [_target performSelector:_action withObject:self]
);
Run Code Online (Sandbox Code Playgroud)
ser*_*gio 209
我对此的猜测是这样的:由于编译器不知道选择器,ARC无法强制执行适当的内存管理.
实际上,有时候内存管理通过特定约定与方法的名称相关联.具体来说,我正在考虑方便构造函数与make方法; 前者按惯例返回自动释放的对象; 后者是保留的对象.约定基于选择器的名称,因此如果编译器不知道选择器,则它无法强制执行适当的内存管理规则.
如果这是正确的,我认为您可以安全地使用您的代码,前提是您确保内存管理的一切正常(例如,您的方法不返回它们分配的对象).
0xc*_*ced 121
在项目构建设置中,在其他警告标志(WARNING_CFLAGS)下,添加
-Wno-arc-performSelector-leaks
现在只需确保您调用的选择器不会导致保留或复制您的对象.
jlu*_*yiv 111
作为解决方法,直到编译器允许覆盖警告,您可以使用运行时
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));Run Code Online (Sandbox Code Playgroud)
代替
[_controller performSelector:NSSelectorFromString(@"someMethod")];Run Code Online (Sandbox Code Playgroud)
你必须这样做
#import <objc/message.h>Run Code Online (Sandbox Code Playgroud)
Bar*_*ker 88
要仅使用执行选择器在文件中忽略错误,请按如下方式添加#pragma:
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
Run Code Online (Sandbox Code Playgroud)
这会忽略此行上的警告,但仍会在整个项目的其余部分允许它.
mat*_*att 70
奇怪但却是真的:如果可以接受(即结果是无效的,你不介意让runloop循环一次),添加一个延迟,即使这是零:
[_controller performSelector:NSSelectorFromString(@"someMethod")
withObject:nil
afterDelay:0];
Run Code Online (Sandbox Code Playgroud)
这会删除警告,大概是因为它让编译器放心不能返回任何对象并且以某种方式管理不当.
syv*_*vex 34
这是基于上面给出的答案的更新宏.这个应该允许你使用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]
);
Run Code Online (Sandbox Code Playgroud)
Ben*_*hen 31
此代码不涉及编译器标志或直接运行时调用:
SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];
Run Code Online (Sandbox Code Playgroud)
NSInvocation允许设置多个参数,因此不同于performSelector任何方法.
Chr*_*nce 20
好吧,这里有很多答案,但由于这有点不同,结合几个答案,我以为我会把它放进去.我正在使用一个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
Run Code Online (Sandbox Code Playgroud)
Pat*_*ini 16
为了后人的缘故,我决定把帽子扔进戒指:)
最近我看到越来越多的重组远离target/ selector范式,支持协议,块等等.然而,有一个替代品performSelector我已经使用了几次:
[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];
Run Code Online (Sandbox Code Playgroud)
这些似乎是一个干净的,ARC安全的,几乎相同的替代品,performSelector无需太多objc_msgSend().
虽然,我不知道iOS上是否有模拟器.
c r*_*ald 15
Matt Galloway在这个帖子上的答案解释了原因:
考虑以下:
Run Code Online (Sandbox Code Playgroud)id anotherObject1 = [someObject performSelector:@selector(copy)]; id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];现在,ARC如何知道第一个返回一个保留计数为1的对象,但第二个返回一个自动释放的对象?
如果忽略返回值,通常可以安全地抑制警告.如果你真的需要从performSelector获取一个保留对象,我不确定最佳实践是什么 - 除了"不要那样做".
Pav*_*pov 14
@ 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;
}
Run Code Online (Sandbox Code Playgroud)
在我的示例中导致内存泄漏的唯一方法是CopyDummyWithLeak.原因是ARC不知道,copySelector返回保留的对象.
如果您将运行内存泄漏工具,您可以看到以下图片:
......在任何其他情况下都没有内存泄漏:

让Scott Thompson的宏更通用:
// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)
#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
)
Run Code Online (Sandbox Code Playgroud)
修补编译器有不少于12种替代解决方案.
虽然你在第一次实施时很聪明,但地球上很少有工程师可以跟随你的脚步,而这段代码最终会破裂.
安全路线:
所有这些解决方案都可以使用,与您最初的意图有一定程度的差异.如果您愿意,param可以假设nil:
安全路线,相同的概念行为:
// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
Run Code Online (Sandbox Code Playgroud)
安全路线,行为略有不同:
(参见此回复)
使用任何线程代替[NSThread mainThread].
// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
[_controller performSelectorInBackground:selector withObject:anArgument];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
Run Code Online (Sandbox Code Playgroud)
危险的路线
需要某种编译器静默,这必然会破坏.请注意,目前它在Swift中确实破了.
// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
175325 次 |
| 最近记录: |