ios performSelector 可能会导致泄漏,因为它的选择器是未知的

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7017281/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:03:13  来源:igfitidea点击:

performSelector may cause a leak because its selector is unknown

iosobjective-cmemory-leaksautomatic-ref-counting

提问by Eduardo Scoz

I'm getting the following warning by the ARC compiler:

我收到 ARC 编译器的以下警告:

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

Here's what I'm doing:

这是我在做什么:

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

Why do I get this warning? I understand the compiler can't check if the selector exists or not, but why would that cause a leak? And how can I change my code so that I don't get this warning anymore?

为什么我会收到此警告?我知道编译器无法检查选择器是否存在,但是为什么会导致泄漏?我该如何更改我的代码,以便不再收到此警告?

采纳答案by wbyoung

Solution

解决方案

The compiler is warning about this for a reason. It's very rare that this warning should simply be ignored, and it's easy to work around. Here's how:

编译器对此发出警告是有原因的。这种警告很少会被忽略,而且很容易解决。就是这样:

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

Or more tersely (though hard to read & without the guard):

或者更简洁(虽然很难阅读并且没有警卫):

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

Explanation

解释

What's going on here is you're asking the controller for the C function pointer for the method corresponding to the controller. All NSObjects respond to methodForSelector:, but you can also use class_getMethodImplementationin the Objective-C runtime (useful if you only have a protocol reference, like id<SomeProto>). These function pointers are called IMPs, and are simple typedefed 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.

这里发生的事情是您向控制器询问与控制器对应的方法的 C 函数指针。所有NSObjects 都响应methodForSelector:,但您也可以class_getMethodImplementation在 Objective-C 运行时中使用(如果您只有协议引用,例如 ,则很有用id<SomeProto>)。这些函数指针称为IMPs,并且是简单的typedefed 函数指针 ( id (*IMP)(id, SEL, ...)) 1。这可能接近方法的实际方法签名,但并不总是完全匹配。

Once you have the IMP, you need to cast it to a function pointer that includes all of the details that ARC needs (including the two implicit hidden arguments selfand _cmdof every Objective-C method call). This is handled in the third line (the (void *)on the right hand side simply tells the compiler that you know what you're doing and not to generate a warning since the pointer types don't match).

获得 后IMP,您需要将其转换为一个函数指针,该指针包含 ARC 需要的所有细节(包括两个隐式隐藏参数self_cmd每个 Objective-C 方法调用)。这在第三行中处理((void *)右侧的只是告诉编译器您知道自己在做什么,并且由于指针类型不匹配而不会生成警告)。

Finally, you call the function pointer2.

最后,调用函数指针2

Complex Example

复杂的例子

When the selector takes arguments or returns a value, you'll have to change things a bit:

When the selector takes arguments or returns a value, you'll have to change things a bit:

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;

Reasoning for Warning

警告的推理

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

出现此警告的原因是,对于 ARC,运行时需要知道如何处理您正在调用的方法的结果。结果可以是任何:voidintcharNSString *id等。ARC 通常从您正在使用的对象类型的标头中获取此信息。3

There are really only 4 things that ARC would consider for the return value:4

对于返回值,ARC 只考虑 4 件事:4

  1. Ignore non-object types (void, int, etc)
  2. Retain object value, then release when it is no longer used (standard assumption)
  3. Release new object values when no longer used (methods in the init/ copyfamily or attributed with ns_returns_retained)
  4. Do nothing & assume returned object value will be valid in local scope (until inner most release pool is drained, attributed with ns_returns_autoreleased)
  1. 忽略非对象类型(voidint等)
  2. 保留对象值,不再使用时释放(标准假设)
  3. 不再使用时释放新的对象值(init/copy系列中的方法或归因于ns_returns_retained
  4. 什么都不做并假设返回的对象值在本地范围内有效(直到最内部的释放池耗尽,归因于ns_returns_autoreleased

The call to methodForSelector:assumes that the return value of the method it's calling is an object, but does not retain/release it. So you could end up creating a leak if your object is supposed to be released as in #3 above (that is, the method you're calling returns a new object).

调用methodForSelector:假定它调用的方法的返回值是一个对象,但不保留/释放它。因此,如果您的对象应该像上面的 #3 那样被释放(即您调用的方法返回一个新对象),那么您最终可能会造成泄漏。

For selectors you're trying to call that return voidor other non-objects, you could enable compiler features to ignore the warning, but it may be dangerous. I've seen Clang go through a few iterations of how it handles return values that aren't assigned to local variables. There's no reason that with ARC enabled that it can't retain and release the object value that's returned from methodForSelector:even though you don't want to use it. From the compiler's perspective, it is an object after all. That means that if the method you're calling, someMethod, is returning a non object (including void), you could end up with a garbage pointer value being retained/released and crash.

对于您尝试调用返回值void或其他非对象的选择器,您可以启用编译器功能以忽略警告,但这可能很危险。我已经看到 Clang 对它如何处理未分配给局部变量的返回值进行了几次迭代。没有理由在启用 ARC 的情况下,methodForSelector:即使您不想使用它,它也无法保留和释放从中返回的对象值。从编译器的角度来看,它毕竟是一个对象。这意味着,如果您正在调用的方法someMethod, 返回非对象(包括void),您最终可能会保留/释放垃圾指针值并崩溃。

Additional Arguments

附加参数

One consideration is that this is the same warning will occur with performSelector:withObject:and you could run into similar problems with not declaring how that method consumes parameters. ARC allows for declaring consumed parameters, and if the method consumes the parameter, you'll probably eventually send a message to a zombie and crash. There are ways to work around this with bridged casting, but really it'd be better to simply use the IMPand function pointer methodology above. Since consumed parameters are rarely an issue, this isn't likely to come up.

一个考虑是,这会发生相同的警告performSelector:withObject:,如果不声明该方法如何使用参数,您可能会遇到类似的问题。ARC 允许声明消耗的参数,如果该方法消耗参数,您可能最终会向僵尸发送消息并崩溃。有一些方法可以通过桥接转换来解决这个问题,但实际上最好简单地使用IMP上面的函数指针方法。由于消耗的参数很少成为问题,因此不太可能出现。

Static Selectors

静态选择器

Interestingly, the compiler will not complain about selectors declared statically:

有趣的是,编译器不会抱怨静态声明的选择器:

[_controller performSelector:@selector(someMethod)];

The reason for this is because the compiler actually is able to record all of the information about the selector and the object during compilation. It doesn't need to make any assumptions about anything. (I checked this a year a so ago by looking at the source, but don't have a reference right now.)

这样做的原因是因为编译器实际上能够在编译期间记录有关选择器和对象的所有信息。它不需要对任何事情做出任何假设。(我一年前通过查看来源检查了这一点,但现在没有参考。)

Suppression

抑制

In trying to think of a situation where suppression of this warning would be necessary and good code design, I'm coming up blank. Someone please share if they have had an experience where silencing this warning was necessary (and the above doesn't handle things properly).

在尝试考虑抑制此警告是必要的和良好的代码设计的情况时,我出现了空白。如果有人有过需要消除此警告的经验(并且上述内容处理不当),请分享他们的经验。

More

更多的

It's possible to build up an NSMethodInvocationto handle this as well, but doing so requires a lot more typing and is also slower, so there's little reason to do it.

也可以建立一个NSMethodInvocation来处理这个问题,但是这样做需要更多的输入并且速度也更慢,所以没有理由这样做。

History

历史

When the performSelector:family of methods was first added to Objective-C, ARC did not exist. While creating ARC, Apple decided that a warning should be generated for these methods as a way of guiding developers toward using other means to explicitly define how memory should be handled when sending arbitrary messages via a named selector. In Objective-C, developers are able to do this by using C style casts on raw function pointers.

performSelector:方法系列第一次被添加到 Objective-C 时,ARC 并不存在。在创建 ARC 时,Apple 决定应该为这些方法生成警告,以此引导开发人员使用其他方式明确定义在通过命名选择器发送任意消息时应如何处理内存。在 Objective-C 中,开发人员可以通过对原始函数指针使用 C 风格的强制转换来实现这一点。

With the introduction of Swift, Apple has documentedthe performSelector:family of methods as "inherently unsafe" and they are not available to Swift.

随着 Swift 的推出,Apple已将这一performSelector:系列方法记录为“本质上不安全”,并且它们不适用于 Swift。

Over time, we have seen this progression:

随着时间的推移,我们已经看到了这种进展:

  1. Early versions of Objective-C allow performSelector:(manual memory management)
  2. Objective-C with ARC warns for use of performSelector:
  3. Swift does not have access to performSelector:and documents these methods as "inherently unsafe"
  1. 早期版本的 Objective-C 允许performSelector:(手动内存管理)
  2. 带有 ARC 的 Objective-C 警告使用 performSelector:
  3. Swift 无法访问performSelector:这些方法并将其记录为“本质上不安全”

The idea of sending messages based on a named selector is not, however, an "inherently unsafe" feature. This idea has been used successfully for a long time in Objective-C as well as many other programming languages.

然而,基于命名选择器发送消息的想法并不是“本质上不安全”的特性。这个想法已经在 Objective-C 以及许多其他编程语言中成功使用了很长时间。



1All Objective-C methods have two hidden arguments, selfand _cmdthat are implicitly added when you call a method.

1所有Objective-C 方法都有两个隐藏参数,self并且_cmd在调用方法时隐式添加。

2Calling a NULLfunction 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 IMPfrom 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.

2NULL在 C 中调用函数是不安全的。用于检查控制器是否存在的守卫确保我们拥有一个对象。因此,我们知道我们会得到一个IMPfrom methodForSelector:(尽管它可能是_objc_msgForward进入消息转发系统的入口)。基本上,有了守卫,我们知道我们有一个函数可以调用。

3Actually, it's possible for it to get the wrong info if declare you objects as idand 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.

3实际上,如果将对象声明为 asid而您没有导入所有标头,则可能会得到错误的信息。您最终可能会在编译器认为没问题的代码中崩溃。这是非常罕见的,但可能会发生。通常你只会得到一个警告,它不知道从两个方法签名中选择哪一个。

4See the ARC reference on retained return valuesand unretained return valuesfor more details.

4有关更多详细信息,请参阅有关保留返回值和未保留返回值的 ARC 参考。

回答by Scott Thompson

In the LLVM 3.0 compiler in Xcode 4.2 you can suppress the warning as follows:

在 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

If you're getting the error in several places, and want to use the C macro system to hide the pragmas, you can define a macro to make it easier to suppress the warning:

如果您在多个地方遇到错误,并且想要使用 C 宏系统来隐藏编译指示,您可以定义一个宏来更轻松地抑制警告:

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

You can use the macro like this:

您可以像这样使用宏:

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

If you need the result of the performed message, you can do this:

如果您需要执行消息的结果,您可以这样做:

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

回答by sergio

My guess about this is this: since the selector is unknown to the compiler, ARC cannot enforce proper memory management.

我对此的猜测是:由于编译器不知道选择器,因此 ARC 无法强制执行正确的内存管理。

In fact, there are times when memory management is tied to the name of the method by a specific convention. Specifically, I am thinking of convenience constructorsversus makemethods; the former return by convention an autoreleased object; the latter a retained object. The convention is based on the names of the selector, so if the compiler does not know the selector, then it cannot enforce the proper memory management rule.

事实上,有时内存管理通过特定的约定与方法的名称相关联。具体来说,我正在考虑便利构造函数make方法;前者按照惯例返回一个自动释放的对象;后者是一个保留对象。该约定基于选择器的名称,因此如果编译器不知道选择器,则它无法强制执行正确的内存管理规则。

If this is correct, I think that you can safely use your code, provided you make sure that everything is ok as to memory management (e.g., that your methods do not return objects that they allocate).

如果这是正确的,我认为您可以安全地使用您的代码,前提是您确保内存管理一切正常(例如,您的方法不返回它们分配的对象)。

回答by 0xced

In your project Build Settings, under Other Warning Flags(WARNING_CFLAGS), add
-Wno-arc-performSelector-leaks

在项目生成设置,下等警示标志WARNING_CFLAGS),加
-Wno-arc-performSelector-leaks

Now just make sure that the selector you are calling does not cause your object to be retained or copied.

现在只需确保您正在调用的选择器不会导致您的对象被保留或复制。

回答by jluckyiv

As a workaround until the compiler allows overriding the warning, you can use the runtime

作为在编译器允许覆盖警告之前的解决方法,您可以使用运行时

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

instead of

代替

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

You'll have to

你必须

#import <objc/message.h>

回答by Barlow Tucker

To ignore the error only in the file with the perform selector, add a #pragma as follows:

要仅忽略带有执行选择器的文件中的错误,请添加 #pragma 如下:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

This would ignore the warning on this line, but still allow it throughout the rest of your project.

这将忽略此行上的警告,但在项目的其余部分仍然允许它。

回答by matt

Strange but true: if acceptable (i.e. result is void and you don't mind letting the runloop cycle once), add a delay, even if this is zero:

奇怪但真实:如果可以接受(即结果无效并且您不介意让 runloop 循环一次),添加延迟,即使这是零:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

This removes the warning, presumably because it reassures the compiler that no object can be returned and somehow mismanaged.

这消除了警告,大概是因为它向编译器保证没有对象可以返回并且以某种方式管理不善。

回答by syvex

Here is an updated macro based on the answer given above. This one should allow you to wrap your code even with a return statement.

这是基于上面给出的答案的更新宏。这应该允许您甚至使用 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]
);

回答by Benedict Cohen

This code doesn't involve compiler flags or direct runtime calls:

此代码不涉及编译器标志或直接运行时调用:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationallows multiple arguments to be set so unlike performSelectorthis will work on any method.

NSInvocation允许设置多个参数,因此与performSelector此不同,它适用于任何方法。

回答by Chris Prince

Well, lots of answers here, but since this is a little different, combining a few answers I thought I'd put it in. I'm using an NSObject category which checks to make sure the selector returns void, and also suppresses the compiler warning.

嗯,这里有很多答案,但是由于这有点不同,所以我想结合一些答案,我想我会把它放进去。 我正在使用 NSObject 类别,它检查以确保选择器返回无效,并抑制编译器警告。

#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