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

发布于 2024-11-28 17:13:29 字数 311 浏览 0 评论 0原文

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

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

这就是我正在做的事情:

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

为什么我会收到此警告?我知道编译器无法检查选择器是否存在,但是为什么这会导致泄漏呢?我怎样才能改变我的代码,这样我就不会再收到这个警告了?

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

"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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

月亮邮递员 2024-12-05 17:13:29

解决方案

编译器对此发出警告是有原因的。简单地忽略此警告的情况很少见,而且很容易解决。方法如下:

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);

解释

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

获得 IMP 后,您需要将其转换为函数指针,其中包含 ARC 所需的所有详细信息(包括两个隐式隐藏参数 self每个 Objective-C 方法调用的 _cmd)。这是在第三行中处理的(右侧的 (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;

警告的原因 出现

此警告的原因是,使用 ARC,运行时需要知道如何处理您所选择的方法的结果。正在打电话。结果可以是任何内容:voidintcharNSString *id > 等。ARC 通常从您正在使用的对象类型的标头获取此信息。3

对于返回值,ARC 实际上只考虑 4 件事:4< /sup>

  1. 忽略非对象类型(voidint 等)
  2. 保留对象值,然后在不再使用时释放它(标准假设)
  3. 不再使用时释放新的对象值(< code>init/ copy 系列或归因于 ns_returns_retained)
  4. 不执行任何操作 &假设返回的对象值在本地范围内有效(直到最内部的释放池被耗尽,归因于ns_returns_autoreleased

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

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

其他参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];

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

抑制

在尝试考虑抑制此警告是必要的并且良好的代码设计的情况时,我出现空白。如果有人有过需要消除此警告的经验(并且上述方法不能正确处理问题),请分享。

更多

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

历史

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

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

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

  1. Objective-C 的早期版本允许 performSelector: (手动内存管理)
  2. Objective-C with ARC 警告使用 performSelector:
  3. Swift 确实如此无法访问 performSelector: 并将这些方法记录为“本质上不安全”

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


1 所有 Objective-C 方法都有两个隐藏参数,self_cmd,它们在调用方法时隐式添加。

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

3 实际上,如果将对象声明为 id 并且没有导入所有标头,它可能会获取错误的信息。您最终可能会在编译器认为正常的代码中发生崩溃。这种情况非常罕见,但也有可能发生。通常您只会收到一条警告,表明它不知道要选择两个方法签名中的哪一个。

4 请参阅有关保留返回值<的 ARC 参考/a> 和 未保留返回值以获取更多详细信息。

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_getMethodImplementation in 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.

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 self and _cmd of 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).

Finally, you call the function pointer2.

Complex Example

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

There are really only 4 things that ARC would consider for the return value: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/ copy family 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)

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).

For selectors you're trying to call that return void or 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.

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 IMP and function pointer methodology above. Since consumed parameters are rarely an issue, this isn't likely to come up.

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 NSMethodInvocation to handle this as well, but doing so requires a lot more typing and is also slower, so there's little reason to do it.

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.

With the introduction of Swift, Apple has documented the performSelector: family of methods as "inherently unsafe" and they are not available to 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"

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.


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.

听风念你 2024-12-05 17:13:29

在 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

如果您在多个位置收到错误,并且想要使用 C 宏系统来隐藏编译指示,您可以定义一个宏以使其更容易抑制警告:

#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]
);

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

#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:

#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]
);
谢绝鈎搭 2024-12-05 17:13:29

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

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

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

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

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 constructors versus make methods; 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.

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).

过潦 2024-12-05 17:13:29

在您的项目构建设置中的其他警告标志 (WARNING_CFLAGS) 下,添加
-Wno-arc-performSelector-leaks

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

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

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

赴月观长安 2024-12-05 17:13:29

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

您需要标题:

#import <objc/message.h>

然后尝试以下:

// For strict compilers.
((id(*)(id,SEL))objc_msgSend)(_controller, sel_getUid("someMethod"));

或而

// Old answer's code:
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

不是:

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

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

You need header:

#import <objc/message.h>

Then try below:

// For strict compilers.
((id(*)(id,SEL))objc_msgSend)(_controller, sel_getUid("someMethod"));

OR

// Old answer's code:
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

Instead of:

[_controller performSelector:NSSelectorFromString(@"someMethod")];
如果没结果 2024-12-05 17:13:29

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

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

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

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

#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.

苏别ゝ 2024-12-05 17:13:29

奇怪但正确:如果可以接受(即结果为空并且您不介意让运行循环循环一次),请添加延迟,即使这是零:

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

这会删除警告,大概是因为它向编译器保证没有对象可以返回并不知何故管理不善。

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:

[_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.

乄_柒ぐ汐 2024-12-05 17:13:29

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

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.

#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]
);
煞人兵器 2024-12-05 17:13:29

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

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

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

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];

NSInvocation allows multiple arguments to be set so unlike performSelector this will work on any method.

想挽留 2024-12-05 17:13:29

好吧,这里有很多答案,但由于这有点不同,因此结合了一些我认为应该放入的答案。我使用 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

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.

#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
比忠 2024-12-05 17:13:29

为了子孙后代,我决定投身其中 :)

最近,我看到越来越多的重组远离 target/selector 范式,有利于协议、块等。但是,有一个我已经使用过几次的 performSelector 的直接替代品:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

它们似乎是一种干净的、ARC 安全的、和几乎相同的替代品performSelector 无需过多使用 objc_msgSend()

不过,我不知道 iOS 上是否有类似的功能。

For posterity's sake, I've decided to throw my hat into the ring :)

Recently I've been seeing more and more restructuring away from the target/selector paradigm, in favor of things such as protocols, blocks, etc. However, there is one drop-in replacement for performSelector that I've used a few times now:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

These seem to be a clean, ARC-safe, and nearly identical replacement for performSelector without having to much about with objc_msgSend().

Though, I have no idea if there is an analog available on iOS.

浅听莫相离 2024-12-05 17:13:29

Matt Galloway 在此帖子上的回答解释了原因:

考虑以下因素:

id anotherObject1 = [someObject PerformSelector:@selector(copy)];
id anotherObject2 = [someObject PerformSelector:@selector(giveMeAnotherNonRetainedObject)];

现在,ARC 如何知道第一个返回的对象的保留计数为 1,而第二个则返回一个对象
返回一个自动释放的对象?

如果您忽略返回值,那么抑制警告通常是安全的。如果您确实需要从 PerformSelector 获取保留的对象,我不确定最佳实践是什么 - 除了“不要这样做”。

Matt Galloway's answer on this thread explains the why:

Consider the following:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

Now, how can ARC know that the first returns an object with a retain count of 1 but the second
returns an object which is autoreleased?

It seems that it is generally safe to suppress the warning if you are ignoring the return value. I'm not sure what the best practice is if you really need to get a retained object from performSelector -- other than "don't do that".

盗琴音 2024-12-05 17:13:29

@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 返回保留的对象。

如果您运行内存泄漏工具,您可以看到下图:
在此处输入图像描述
...并且在任何其他情况下都没有内存泄漏:
在此处输入图像描述

@c-road provides the right link with problem description here. Below you can see my example, when performSelector causes a memory leak.

@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;
}

The only method, which causes memory leak in my example is CopyDummyWithLeak. The reason is that ARC doesn't know, that copySelector returns retained object.

If you'll run Memory Leak Tool you can see the following picture:
enter image description here
...and there are no memory leaks in any other case:
enter image description here

神回复 2024-12-05 17:13:29

不要压制警告!

修补编译器的替代解决方案不少于 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]];

安全路线,略有不同的行为:< /strong>

(请参阅回复)
使用任何线程代替[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]];

危险路线

需要某种编译器静默,这必然会被破坏。请注意,目前,它确实Swift中中断。

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

Do not suppress warnings!

There are no less than 12 alternative solutions to tinkering with the compiler.
While you are being clever at the time the first implementation, few engineer on Earth can follow your footsteps, and this code will eventually break.

Safe Routes:

All these solutions will work, with some degree of of variation from your original intent. Assume that param can be nil if you so desire:

Safe route, same conceptual behavior:

// 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]];

Safe route, slightly different behavior:

(See this response)
Use any thread in lieu of [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]];

Dangerous Routes

Requires some kind of compiler silencing, which is bound to break. Note that at present time, it did break in Swift.

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
说谎友 2024-12-05 17:13:29

为了使 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")

然后像这样使用它:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

To make Scott Thompson's macro more generic:

// 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")

Then use it like this:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )
忘年祭陌 2024-12-05 17:13:29

由于您使用的是 ARC,因此您必须使用 iOS 4.0 或更高版本。这意味着您可以使用块。如果您不记住要执行的选择器,而是选择一个块,ARC 将能够更好地跟踪实际发生的情况,并且您不必冒意外引入内存泄漏的风险。

Because you are using ARC you must be using iOS 4.0 or later. This means you could use blocks. If instead of remembering the selector to perform you instead took a block, ARC would be able to better track what is actually going on and you wouldn't have to run the risk of accidentally introducing a memory leak.

燃情 2024-12-05 17:13:29

我不使用块方法,这给我带来了一些问题:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

我将使用 NSInitation,如下所示:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

Instead of using the block approach, which gave me some problems:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

I will use NSInvocation, like this:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }
极致的悲 2024-12-05 17:13:29

如果您不需要传递任何参数,一个简单的解决方法是使用 valueForKeyPath。这甚至在 Class 对象上也是可能的。

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

If you don't need to pass any arguments an easy workaround is to use valueForKeyPath. This is even possible on a Class object.

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}
淡莣 2024-12-05 17:13:29

您还可以在此处使用协议。因此,创建一个像这样的协议:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

在需要调用选择器的类中,您有一个@property。

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

当您需要在 MyObject 实例中调用 @selector(doSomethingWithObject:) 时,请执行以下操作:

[self.source doSomethingWithObject:object];

You could also use a protocol here. So, create a protocol like so:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

In your class that needs to call your selector, you then have a @property.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

When you need to call @selector(doSomethingWithObject:) in an instance of MyObject, do this:

[self.source doSomethingWithObject:object];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文