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

performSelector may cause a leak because its selector is unknown

提问人:Eduardo Scoz 提问时间:8/11/2011 最后编辑:James WebsterEduardo Scoz 更新时间:10/20/2021 访问量:190293

问:

ARC编译器向我发出以下警告:

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

这是我正在做的:

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

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

ios objective-c 内存泄漏自动 引用计数

评论

3赞 Eduardo Scoz 8/11/2011
变量的名称是动态的,它取决于很多其他因素。有一种风险是我称之为不存在的东西,但这不是问题。
7赞 Eduardo Scoz 8/11/2011
@matt为什么在对象上动态调用方法会是一种不好的做法呢?NSSelectorFromString() 的全部目的不就是支持这种做法吗?
7赞 mattacular 8/11/2011
在通过 performSelector 设置之前,您还应该/可以测试 [_controller respondsToSelector:mySelector]:
50赞 ctpenrose 4/12/2012
@mattacular 希望我能投反对票:“那......是不好的做法。
7赞 Chris Page 5/23/2012
如果您知道字符串是文字,只需使用 @selector(),以便编译器可以知道选择器名称是什么。如果实际代码使用在运行时构造或提供的字符串调用 NSSelectorFromString(),则必须使用 NSSelectorFromString()。

答:

208赞 sergio 8/11/2011 #1

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

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

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

评论

5赞 Eduardo Scoz 8/11/2011
感谢您的回答,我会对此进行更多研究,看看发生了什么。关于如何绕过警告并使其消失的任何想法?我讨厌在我的代码中永远放置警告,因为什么是安全调用。
84赞 Eduardo Scoz 8/12/2011
因此,我从苹果的论坛中得到证实,情况确实如此。他们将添加一个被遗忘的覆盖,以允许人们在将来的版本中禁用此警告。谢谢。
5赞 aroth 2/4/2012
这个答案提出了一些问题,比如如果 ARC 试图根据约定和方法名称来决定何时发布某些内容,那么它是如何“引用计数”的?如果 ARC 假设代码遵循某种约定,而不是无论遵循什么约定,实际上都要跟踪引用,那么您描述的行为听起来只比完全任意的行为好一点。
8赞 Rob Napier 2/16/2012
ARC 在编译时自动执行添加保留和释放的过程。它不是垃圾回收(这就是为什么它如此快速且开销如此之低)。它一点也不武断。默认规则基于几十年来一直应用的完善的 ObjC 约定。这样就无需向每个方法显式添加解释其内存管理的方法。但这也使得编译者无法正确处理这种模式(这种模式曾经很常见,但近年来已被更强大的模式所取代)。__attribute
8赞 Nicolas Miari 6/22/2012
因此,我们不能再拥有类型的 ivar 并根据情况分配不同的选择器?一路走好,动态语言......SEL
110赞 jluckyiv 8/16/2011 #2

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

您需要标头:

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

评论

9赞 jluckyiv 8/19/2011
ARC 识别 Cocoa 约定,然后根据这些约定添加保留和释放。由于 C 不遵循这些约定,因此 ARC 会强制您使用手动内存管理技术。如果创建 CF 对象,则必须对其进行 CFRelease()。如果dispatch_queue_create(),则必须dispatch_release()。总而言之,如果你想避免ARC警告,你可以通过使用C对象和手动内存管理来避免它们。此外,还可以通过对该文件使用 -fno-objc-arc 编译器标志来禁用每个文件的 ARC。
8赞 bbum 9/28/2011
不是没有铸造,你不能。Varargs 与显式类型的参数列表不同。它通常会因巧合而起作用,但我不认为“巧合”是正确的。
21赞 0xced 11/16/2011
不要那样做,也不等同!看看方法签名不匹配Objective-C 弱类型的一大弱点,他们正在深入解释这个问题。[_controller performSelector:NSSelectorFromString(@"someMethod")];objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
6赞 Matt Gallagher 2/17/2012
@0xced 在这种情况下,没关系。objc_msgSend不会为任何在 performSelector: 或其变体中正常工作的选择器创建方法签名不匹配,因为它们只将对象作为参数。只要所有参数都是指针(包括对象)、doubles 和 NSInteger/long,并且返回类型为 void、pointer 或 long,那么objc_msgSend就可以正常工作。
0赞 Ol Sen 12/30/2020
ObjC 不像 c++ 那样具有函数重载。因此,即使认为 mikeash 的网站表达了真正的担忧,当你尝试重载(不是意味着重写 - 如果有人混合了这些词)由于 ObjC 而无法重载的方法时,你应该收到编译器警告。
1190赞 Scott Thompson 10/29/2011 #3

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

评论

0赞 Eric 5/26/2012
当优化设置为“无”以外的任何值时,此方法可能会导致内存泄漏。
4赞 Andrey Tarantsov 8/20/2012
@Eric 不,它不能,除非你调用有趣的方法,如“initSomething”或“newSomething”或“somethingCopy”。
4赞 Emil 1/11/2013
@Julian 这确实有效,但这会关闭整个文件的警告 - 您可能不需要或不希望这样做。用 和 -pragmas 包装它更干净、更安全。poppush
2赞 Andra Todorescu 5/31/2014
这样做只是使编译器静音。这并不能解决问题。如果选择器不存在,你就完蛋了。
2赞 7/23/2014
仅当被或类似逻辑包装时,才应使用此方法。if ([_target respondsToSelector:_selector]) {
121赞 0xced 10/31/2011 #4

在项目的 Build Settings 中,在 Other Warning Flags () 下,添加WARNING_CFLAGS
-Wno-arc-performSelector-leaks

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

评论

13赞 Michael 1/5/2012
请注意,您可以为特定文件(而不是整个项目)添加相同的标志。如果在“生成阶段”->编译源下查看,则可以设置每个文件的编译器标志(就像要从 ARC 中排除文件一样)。在我的项目中,只有一个文件应该以这种方式使用选择器,所以我只是排除了它并保留了其他文件。
4赞 honus 11/23/2011 #5

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

评论

0赞 tc. 5/11/2012
实际上,块很容易意外地产生 ARC 无法解决的保留循环。我仍然希望当您通过 ivar 隐式使用时有一个编译器警告(例如 而不是 )。selfivarself->ivar
0赞 OrangeDog 7/8/2015
你的意思是像 -Wimplicit-retain-self ?
88赞 Barlow Tucker 1/19/2012 #6

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

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

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

评论

6赞 Rob 4/8/2012
我认为您也可以在有问题的方法之后立即重新打开警告。我知道如果我关闭警告,我喜欢尽快重新打开它,这样我就不会意外地让另一个意想不到的警告溜走。这不太可能是一个问题,但这只是我每次关闭警告时的做法。#pragma clang diagnostic warning "-Warc-performSelector-leaks"
2赞 deanWombourne 7/11/2012
您还可以在进行任何更改之前使用来还原以前的编译器配置状态,并还原以前的状态。如果您要关闭加载并且不希望在代码中包含大量重新启用编译指示行,则非常有用。#pragma clang diagnostic warning push#pragma clang diagnostic warning pop
0赞 hfossli 12/10/2012
它只会忽略以下行吗?
31赞 Benedict Cohen 2/1/2012 #7

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

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

评论

4赞 Aaron Brager 1/16/2013
您是否知道这是否真的解决了相关的内存管理问题,或者它是否存在相同的问题,但 Xcode 不够智能,无法用此代码警告您?
1赞 Mihai Timar 4/10/2013
你可以说它解决了内存管理问题;但这是因为它基本上允许您指定行为。例如,您可以选择是否让调用保留参数。据我所知,它试图通过相信您知道自己在做什么并且不向其提供不正确的数据来解决可能出现的签名不匹配问题。我不确定是否可以在运行时执行所有检查。正如在另一条评论中提到的,mikeash.com/pyblog/...很好地解释了不匹配会造成什么。
16赞 Patrick Perini 2/26/2012 #8

为了子孙后代,我决定把帽子扔进擂台:)

最近,我看到越来越多的重组远离 / 范式,转而支持协议、块等。但是,我现在已经使用过几次了:targetselectorperformSelector

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

这些似乎是一个干净的、ARC 安全的、几乎相同的替代品,而不必过多地使用 .performSelectorobjc_msgSend()

不过,我不知道 iOS 上是否有可用的模拟。

评论

6赞 Eduardo Scoz 2/27/2012
感谢您包括这个..它在 iOS 中可用:。我曾经研究过它,但是在您的域或服务的中间使用与 UI 相关的类只是为了进行动态调用,这感觉有点尴尬。感谢您包括此内容![[UIApplication sharedApplication] sendAction: to: from: forEvent:]
2赞 tc. 5/11/2012
哎呀!它将有更多的开销(因为它需要检查该方法是否可用,如果它不可用,则沿着响应器链向上走)并且具有不同的错误行为(如果找不到任何响应该方法的内容,则返回 NO,而不是简单地崩溃)。当您想要从id-performSelector:...
2赞 matt 10/18/2012
@tc。它不会“走上响应者链”,除非是 nil,但事实并非如此。它只是直接进入目标对象,无需事先检查。因此,没有“更多的开销”。这不是一个很好的解决方案,但你给出的理由不是原因。:)to:
15赞 c roald 5/16/2012 #9

马特·加洛韦(Matt Galloway)对此线程的回答解释了原因:

请考虑以下几点:

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

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

如果您忽略返回值,似乎通常可以安全地禁止显示警告。我不确定如果您真的需要从 performSelector 获取保留对象,那么最佳实践是什么——除了“不要这样做”。

71赞 matt 11/12/2012 #10

奇怪但真实:如果可以接受(即结果是无效的,你不介意让运行循环循环一次),添加一个延迟,即使这是零:

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

这会删除警告,大概是因为它向编译器保证,不会返回任何对象,并且以某种方式管理不善。

评论

3赞 Aaron Brager 1/16/2013
您是否知道这是否真的解决了相关的内存管理问题,或者它是否存在相同的问题,但 Xcode 不够智能,无法用此代码警告您?
0赞 Florian 4/9/2013
这在语义上不是一回事!使用 performSelector:withObject:AfterDelay: 将在下次运行循环时执行选择器。因此,此方法会立即返回。
12赞 matt 4/9/2013
@Florian 当然不一样!阅读我的回答:我说是否可以接受,因为结果是无效的,并且运行循环循环。这是我回答的第一句话
14赞 Pavel Osipov 12/25/2012 #11

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

如果要运行内存泄漏工具,则可以看到下图: enter image description here ...并且在任何其他情况下都没有内存泄漏:enter image description here

34赞 syvex 5/7/2013 #12

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

评论

6赞 uasi 10/15/2013
return不必在宏内部; 也有效,看起来更理智。return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
-1赞 Damon 8/3/2013 #13

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

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

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

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

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

[self.source doSomethingWithObject:object];

评论

2赞 Eduardo Scoz 8/5/2013
嘿 Wu,谢谢,但是使用 NSSelectorFromString 的意义在于,当您不知道要在运行时调用哪个选择器时。
1245赞 wbyoung 11/19/2013 #14

溶液

编译器对此发出警告是有原因的。很少会忽略此警告,并且很容易解决。方法如下:

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 函数指针。所有 s 都响应 ,但你也可以在 Objective-C 运行时中使用(如果你只有一个协议引用,比如 )。这些函数指针称为 s,是简单的函数指针 ()1。这可能接近方法的实际方法签名,但并不总是完全匹配。NSObjectmethodForSelector:class_getMethodImplementationid<SomeProto>IMPtypedefid (*IMP)(id, SEL, ...)

获得 后,您需要将其转换为函数指针,该指针包含 ARC 所需的所有详细信息(包括两个隐式隐藏参数和每个 Objective-C 方法调用)。这在第三行中处理(右侧只是告诉编译器你知道你在做什么,并且不会生成警告,因为指针类型不匹配)。IMPself_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 时,运行时需要知道如何处理所调用方法的结果。结果可以是任何内容:、、、、等。 ARC 通常从您正在使用的对象类型的标头中获取此信息。3voidintcharNSString *id

对于返回值,ARC 实际上只考虑了 4 件事:4

  1. 忽略非对象类型(、等)voidint
  2. 保留对象值,然后在不再使用时释放(标准假设)
  3. 不再使用时释放新的对象值(/ 系列中的方法或归因于initcopyns_returns_retained)
  4. 什么都不做,并假设返回的对象值在本地范围内有效(直到最内部的发布池被清空,归因于ns_returns_autoreleased)

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

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

其他参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];

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

抑制

在试图考虑一种情况时,抑制此警告是必要的,并且是良好的代码设计,我得出了空白。有人请分享他们是否有过需要静音此警告的经历(并且上述内容无法正确处理事情)。

更多

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

历史

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

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

随着时间的流逝,我们看到了这种进展:

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

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


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

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

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

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

评论

0赞 Tricertops 11/19/2013
@wbyoung 如果你的代码解决了保留问题,我想知道为什么不以这种方式实现方法。它们具有严格的方法签名(返回 ,需要一到两秒),因此不需要处理原始类型。performSelector:idid
1赞 wbyoung 5/13/2014
@Andy根据方法原型的定义处理参数(不会保留/释放)。问题主要基于返回类型。
2赞 Stan James 5/20/2014
使用最新的 Xcode 时,“复杂示例”会出错。(5.1.1) 尽管如此,我还是学到了很多东西!Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
2赞 Davyd Geyl 5/22/2014
void (*func)(id, SEL) = (void *)imp;不编译,我已将其替换为void (*func)(id, SEL) = (void (*)(id, SEL))imp;
1赞 Isaak Osipovich Dunayevsky 1/8/2015
更改为 或void (*func)(id, SEL) = (void *)imp;<…> = (void (*))imp;<…> = (void (*) (id, SEL))imp;
21赞 Chris Prince 2/21/2014 #15

好吧,这里有很多答案,但由于这有点不同,结合一些答案,我想我会把它放进去。我正在使用一个 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

评论

0赞 Rik Renich 11/21/2017
“v”应该用_C_VOID代替吗?_C_VOID在 <objc/runtime.h> 中声明。
2赞 supersabbath 5/21/2015 #16

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

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

我将使用 NSInvocation,如下所示:

    -(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];
    }
6赞 Ben Flynn 8/4/2015 #17

要使 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];
                )

评论

0赞 Scott Thompson 8/20/2015
FWIW,我没有添加宏。有人把它添加到我的回复中。就个人而言,我不会使用宏。编译指示是为了解决代码中的特例,编译指示非常明确和直接地说明正在发生的事情。我更喜欢把它们放在原地,而不是隐藏起来,或者把它们抽象在一个宏后面,但这只是我。YMMV。
0赞 Ben Flynn 8/20/2015
@ScottThompson 这很公平。对我来说,在我的代码库中搜索这个宏很容易,我通常还会添加一个未静音的警告来处理潜在的问题。
7赞 SwiftArchitect 12/31/2015 #18

不要禁止显示警告!

有不少于 12 种替代解决方案来修改编译器。
虽然你在第一次实现时很聪明,但地球上很少有工程师可以追随你的脚步,而这段代码最终会崩溃。

安全路线:

所有这些解决方案都将起作用,但与您的原始意图有一定程度的差异。假设如果您愿意,可以:paramnil

安全路线,相同的概念行为:

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

安全路线,行为略有不同:

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

评论

3赞 Bryan Chen 1/1/2016
措辞非常错误。安全的路线一点也不比危险更安全。可以说它更危险,因为它隐含地隐藏了警告。
0赞 SwiftArchitect 1/1/2016
我会将措辞改为不具有侮辱性,但我信守诺言。我发现静音警告唯一可以接受的情况是,如果我不拥有代码。没有一个工程师可以在不了解所有后果的情况下安全地维护静默代码,这意味着阅读这个论点,这种做法显然是有风险的;特别是如果您考虑 12 种简单的英语、强大的替代方案。
1赞 Bryan Chen 1/1/2016
不。你不明白我的意思。使用不是使警告静音的好方法,而且有副作用。(它不能解决内存泄漏)extra 以非常明确的方式显式禁止显示警告。performSelectorOnMainThread#clang diagnostic ignored
0赞 SwiftArchitect 1/1/2016
诚然,在非方法上执行选择器是真正的问题。- (void)
0赞 Catalin 12/7/2016
以及如何通过它调用具有多个参数的选择器并同时确保安全?@SwiftArchitect
2赞 arsenius 2/15/2017 #19

如果您不需要传递任何参数,一个简单的解决方法是使用 .这甚至可以在对象上实现。valueForKeyPathClass

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