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


1258

ARC编译器收到以下警告:

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

这是我在做什么:

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

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


3
变量的名称是动态的,它取决于许多其他因素。有可能我调用不存在的东西,但这不是问题。
爱德华多·斯科兹

6
@matt为什么在对象上动态调用方法是不好的做法?NSSelectorFromString()的全部目的不是为了支持这种做法吗?
爱德华多·斯科兹

7
在通过performSelector设置之前,您还应该/可以测试[_controllers responsesToSelector:mySelector]:
mattacular 2011年

50
@mattacular希望我可以投反对票:“那是不道德的做法。”
ctpenrose

6
如果您知道字符串是文字,则只需使用@selector()即可使编译器知道选择器的名称是什么。如果您的实际代码使用在运行时构造或提供的字符串调用NSSelectorFromString(),则必须使用NSSelectorFromString()。
克里斯·佩奇

Answers:


1211

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

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:,但是您也可以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;

警告推理

产生此警告的原因是,对于ARC,运行时需要知道如何处理所调用方法的结果。其结果可能是什么:voidintcharNSString *id,等ARC通常会从您正在使用的对象类型的报头信息。3

ARC实际上只考虑4个返回值:4

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

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

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

附加参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];

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

抑制

在试图考虑必须取消此警告和良好的代码设计的情况下,我显得空白。如果有人曾有过必须消除此警告的经验(并且上述内容无法正确处理),请与他人分享。

更多

可能也可以构建一个NSMethodInvocation来处理此问题,但是这样做需要更多的键入操作,而且输入速度也较慢,因此没有理由这样做。

历史

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

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

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

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

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


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

2NULL在C语言中调用函数并不安全。用于检查控制器是否存在的防护措施可确保我们有一个对象。因此,我们知道我们将从中获取IMP信息methodForSelector:(尽管可能来自_objc_msgForward,进入消息转发系统)。基本上,有了后卫,我们知道我们要调用一个函数。

3实际上,如果将对象声明为as id并且未导入所有标头,则可能会得到错误的信息。您可能最终会崩溃,导致编译器认为正常的代码崩溃。这是非常罕见的,但有可能发生。通常,您会收到一条警告,提示它不知道从两种方法签名中选择哪一种。

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


@wbyoung如果您的代码解决了保留问题,我想知道为什么performSelector:未以这种方式实现方法。它们具有严格的方法签名(return id,采用1或2 ids),因此不需要处理原始类型。
Tricertops

1
@Andy参数是根据方法原型的定义处理的(不会保留/释放)。关注主要基于返回类型。
wbyoung 2014年

2
Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'使用最新的Xcode时,“复杂示例”给出错误。(5.1.1)我仍然学到了很多东西!
斯坦·詹姆斯

2
void (*func)(id, SEL) = (void *)imp;无法编译,我将其替换为void (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl 2014年

1
更改void (*func)(id, SEL) = (void *)imp;<…> = (void (*))imp;<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky

1182

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

当优化设置为“无”时,此方法可能导致内存泄漏。
埃里克

4
@Eric不行,除非您正在调用“ initSomething”或“ newSomething”或“ somethingCopy”之类的有趣方法。
安德烈·塔兰佐夫

3
@Julian确实可以,但是可以关闭整个文件的警告-您可能不需要也不需要。用-pragma poppushwp-pragma包裹起来会更干净,更安全。
Emil

2
所有这些都是使编译器静音。这不能解决问题。如果选择器不存在,您将大为困惑。
安德拉·托多雷斯库

2
仅当由if ([_target respondsToSelector:_selector]) {逻辑或类似逻辑包装时,才应使用此方法。

208

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

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

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


5
感谢您的回答,我将对此进行更多研究,以了解发生了什么。关于如何绕过警告并使警告消失的任何想法?我不希望将警告永久地放在我的代码中,以确保安全。
爱德华多·斯科兹

84
因此,我在苹果公司的论坛上得到了某人的证实,事实确实如此。他们将添加一个被遗忘的覆盖,以允许人们在将来的版本中禁用此警告。谢谢。
爱德华多·斯科兹

5
这个答案引发了一些问题,例如,如果ARC试图根据约定和方法名称确定何时发布某些内容,那么它如何“引用计数”?如果ARC假定代码遵循某种约定,而不是遵循任何约定实际上跟踪引用,则您描述的行为听起来仅比完全任意更好。
aroth

8
ARC在编译时自动执行添加保留和释放的过程。它不是垃圾收集(这就是为什么它是如此之快和低开销的原因)。这根本不是任意的。默认规则基于已经应用了数十年的完善的ObjC约定。这避免了__attribute向每个方法显式添加一个解释其内存管理的需要。但这也使编译器无法正确处理此模式(该模式过去非常普遍,但近年来已被更强大的模式所取代)。
罗布·纳皮尔

8
因此,我们不能再具有类型的ivar SEL并根据情况分配不同的选择器了吗?要走的路,动态的语言……
Nicolas Miari 2012年

121

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

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


12
请注意,您可以为特定文件而不是整个项目添加相同的标志。如果在“构建阶段”->“编译源”下查看,则可以设置每个文件的编译器标志(就像您要从ARC中排除文件一样)。在我的项目中,只有一个文件应以这种方式使用选择器,因此我只是将其排除在外,而其余文件则保留。
2012年

111

作为一种解决方法,直到编译器允许覆盖警告为止,您可以使用运行时

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

代替

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

你必须

#import <objc/message.h>


8
ARC识别可可约定,然后根据这些约定添加保留和释放。由于C不遵循这些约定,因此ARC强制您使用手动内存管理技术。如果创建CF对象,则必须对其进行CFRelease()。如果您使用dispatch_queue_create(),则必须使用dispatch_release()。底线是,如果要避免ARC警告,可以使用C对象和手动内存管理来避免它们。另外,您可以通过在每个文件上使用-fno-objc-arc编译器标志来禁用ARC。
2011年

8
没有铸造,就不可能。Varargs与显式键入的参数列表不同。通常,这是巧合,但我不认为“巧合”是正确的。
bbum 2011年

21
不要那样做,[_controller performSelector:NSSelectorFromString(@"someMethod")];并且objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));不等同!看一下方法签名不匹配Objective-C弱类型的一个大弱点,他们正在深入解释这个问题。
0xced

5
@ 0xced在这种情况下就可以了。objc_msgSend不会为在performSelector:或其变体中可以正常使用的任何选择器创建方法签名不匹配,因为它们只将对象用作参数。只要您的所有参数都是指针(包括对象),双精度和NSInteger / long,并且您的返回类型为void,指针或long,那么objc_msgSend将可以正常工作。
马特·加拉格尔

88

要仅使用Perform选择器忽略文件中的错误,请添加#pragma,如下所示:

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

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


6
我认为您也可以在使用的方法后立即重新打开警告#pragma clang diagnostic warning "-Warc-performSelector-leaks"。我知道如果我关闭了警告,我希望尽快将其重新打开,因此我不会无意间让另一个意外的警告消失。这不太可能是一个问题,但是每当我关闭警告时,这只是我的做法。
罗布

2
您还可以通过使用#pragma clang diagnostic warning push在进行任何更改之前和#pragma clang diagnostic warning pop恢复以前的状态来恢复以前的编译器配置状态。如果您要关闭负载并且不想在代码中有很多重新启用编译指示行,则很有用。
deanWombourne 2012年

它只会忽略下一行吗?
hfossli 2012年

70

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

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

这消除了警告,大概是因为它使编译器确信没有对象可以返回并且以某种方式进行了错误管理。


2
您是否知道这是否确实解决了相关的内存管理问题,或者是否存在相同的问题,但是Xcode不够聪明,无法用此代码警告您?
亚伦·布拉格

从语义上来说这是不一样的!使用performSelector:withObject:AfterDelay:将在下一次运行循环中执行选择器。因此,此方法立即返回。
Florian

10
@Florian当然不一样!阅读我的回答:我说如果可以接受,因为结果是无效的,并且运行循环会循环。那是我回答的第一句话
马特2013年

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

6
return不必在宏内;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);也可以工作,看起来更健康。
uasi

31

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

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在任何方法上都可以使用。


3
您是否知道这是否确实解决了相关的内存管理问题,或者是否存在相同的问题,但是Xcode不够聪明,无法用此代码警告您?
亚伦·布拉格

1
您可以说它解决了内存管​​理问题;但这是因为它基本上可以让您指定行为。例如,您可以选择让调用保留还是不保留参数。就我目前所知,它试图通过相信您知道自己在做什么并且不向其提供不正确的数据来解决可能出现的签名不匹配问题。我不确定是否所有检查都可以在运行时执行。如另一条评论中所述,mikeash.com / pyblog /…很好地解释了不匹配现象。
Mihai Timar

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

应该用_C_VOID替换'v'吗?_C_VOID在<objc / runtime.h>中声明。
里克·雷尼奇

16

为了后代的缘故,我决定将帽子戴上戒指:)

最近,我已经看到越来越多的结构从target/ selector范式中进行了重组,转而使用诸如协议,块等之类的东西。但是,有一种直接替代品performSelector,我已经使用了几次:

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

这些似乎是一种干净的,ARC安全的并且几乎相同的替代品,performSelector而不必太过注意objc_msgSend()

虽然,我不知道iOS上是否有类似产品。


6
感谢您添加此文件。.它在iOS中可用:[[UIApplication sharedApplication] sendAction: to: from: forEvent:]。我曾经看过它,但是在您的域或服务中间使用与UI相关的类来进行动态调用有点尴尬。尽管如此,谢谢您!
Eduardo Scoz

2
w!它会有更多的开销(因为它需要检查该方法是否可用并在不可用的情况下沿着响应者链),并且具有不同的错误行为(在无法找到任何东西的情况下沿着响应者链返回NO)响应该方法,而不是简单地崩溃)。它还当你想不工作id-performSelector:...
TC。

2
@tc。除非to:为零,否则它不会“走上响应者链”,否则不会为零。它只是直接进入目标对象,无需事先检查。因此,没有“更多的开销”。这不是一个很好的解决方案,但是您给出的原因不是原因。:)
马特2012年

15

马特·加洛韦(Matt Galloway)在此主题上的回答解释了原因:

考虑以下:

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

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

如果您忽略返回值,似乎通常可以禁止显示警告。我不确定如果您确实需要从performSelector中获取保留的对象,那不是最佳做法,而不是“不要那样做”。


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

在我的示例中,导致内存泄漏的唯一方法是CopyDummyWithLeak。原因是ARC不知道,copySelector返回保留的对象。

如果您将运行“内存泄漏工具”,则可以看到以下图片: 在此处输入图片说明 ...并且在任何其他情况下都没有内存泄漏: 在此处输入图片说明


6

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

FWIW,我没有添加宏。有人将其添加到我的回复中。就个人而言,我不会使用宏。编译指示可以解决代码中的特殊情况,而编译指示非常明确,可以直接指示正在发生的情况。我更喜欢将它们保留在适当的位置,而不是隐藏它们或将它们抽象到宏后面,但这就是我自己。YMMV。
Scott Thompson

@ScottThompson公平。对我来说,在我的代码库中搜索此宏很容易,而且我通常还会添加一个不沉默的警告来处理潜在的问题。
本·弗林

6

不要抑制警告!

修补编译器有不少于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]];

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

(请参阅响应)
使用任何线程代替[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
措辞很错误。安全的路线一点也不比危险更安全。可以说它更危险,因为它隐式隐藏了警告。
布赖恩·陈

我会固定措辞,以免侮辱,但我坚持我的话。我唯一可以接受的沉默警告是,如果我不拥有该代码。任何工程师都无法在不了解所有后果的情况下安全地维护沉默的代码,这将意味着阅读此论点,并且这种做法很冒险。特别是如果您考虑使用12种简单的英语健壮的替代方法。
SwiftArchitect

1
不,你没明白我的意思。使用此performSelectorOnMainThread方法不是使警告静音的好方法,并且会产生副作用。(它不能解决内存泄漏问题),额外功能#clang diagnostic ignored 可以非常明确地显式抑制警告。
布赖恩·陈

诚然,对非- (void)方法执行选择器是真正的问题。
SwiftArchitect

以及如何通过该方法调用具有多个参数的选择器,并且同时又安全?@SwiftArchitect
Catalin

4

因为使用的是ARC,所以必须使用的是iOS 4.0或更高版本。这意味着您可以使用块。如果您不记得选择器要执行的操作,而是花了一个大块时间,那么ARC可以更好地跟踪实际发生的情况,而您不必冒意外引入内存泄漏的风险。


实际上,使用块可以很容易地意外创建ARC无法解决的保留周期。我仍然希望self通过ivar 隐式使用编译器警告(例如ivar而不是self->ivar)。
tc。

你的意思是-隐性保留自我?
OrangeDog

2

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

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

1

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

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

-2

您也可以在此处使用协议。因此,创建如下协议:

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

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

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

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

[self.source doSomethingWithObject:object];

2
嘿,吴,谢谢,但是使用NSSelectorFromString的目的是当您在运行时不知道要调用哪个选择器时。
Eduardo Scoz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.