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

Edu*_*coz 1251 memory-leaks objective-c ios automatic-ref-counting

ARC编译器收到以下警告:

"performSelector may cause a leak because its selector is unknown".
Run Code Online (Sandbox Code Playgroud)

这是我正在做的事情:

[_controller performSelector:NSSelectorFromString(@"someMethod")];
Run Code Online (Sandbox Code Playgroud)

为什么我会收到此警告?我理解编译器无法检查选择器是否存在,但为什么会导致泄漏?我怎样才能更改我的代码,以便我不再收到此警告?

wby*_*ung 1206

由于某种原因,编译器会对此发出警告.这个警告应该被忽略,这很容易解决.这是如何做:

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

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

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Run Code Online (Sandbox Code Playgroud)

说明

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

有了之后IMP,需要将它转换为包含ARC所需的所有细节的函数指针(包括两个隐式隐藏参数self_cmd每个Objective-C方法调用).这是在第三行处理的((void *)右侧只是告诉编译器你知道你在做什么而不是因为指针类型不匹配而不生成警告).

最后,调用函数指针2.

复杂的例子

当选择器接受参数或返回值时,您将不得不稍微改变一下:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;
Run Code Online (Sandbox Code Playgroud)

警告的推理

这种警告的原因是,使用ARC,运行时需要知道如何处理您正在调用的方法的结果.其结果可能是什么:void,int,char,NSString *,id,等ARC通常会从您正在使用的对象类型的报头信息.3

ARC确实只有4件事可以考虑返回值:4

  1. 忽略非对象类型(void,int等)
  2. 保留对象值,然后在不再使用时释放(标准假设)
  3. 不再使用时释放新对象值(init/ copyfamily中的方法或属于的方法ns_returns_retained)
  4. 什么都不做,并假设返回的对象值在本地范围内有效(直到最内层的发布池被耗尽,归因于ns_returns_autoreleased)

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

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

附加参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];
Run Code Online (Sandbox Code Playgroud)

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

抑制

在试图考虑这种警告的抑制和良好的代码设计的情况时,我会发现空白.有人请分享,如果他们有经验,需要沉默这个警告(以上情况不妥善处理).

更多

也可以建立一个NSMethodInvocation处理这个,但这样做需要更多的打字,也更慢,所以没有理由这样做.

历史

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

随着Swift的引入,Apple 已将这一performSelector:系列方法记录为"本质上不安全",并且它们不适用于Swift.

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

  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实际上,如果将对象声明为id并且您没有导入所有标题,则可能会获取错误的信息.您最终可能会遇到编译器认为正常的代码崩溃.这种情况非常罕见,但可能会发生.通常,您只会收到警告,表示它不知道可以选择哪两种方法签名.

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

  • "复杂示例"给出了一个错误:当使用最新的Xcode时,无法使用'void*'类型的右值初始化'CGRect(*)类型的变量(__强id,SEL,CGRect,UIView*__ strong)'.(5.1.1)尽管如此,我学到了很多东西! (2认同)
  • `void(*func)(id,SEL)=(void*)imp;`不编译,我用`void(*func)替换它(id,SEL)=(void(*)(id,SEL) )小鬼;` (2认同)

Sco*_*son 1180

在Xcode 4.2中的LLVM 3.0编译器中,您可以按如下方式禁止警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop
Run Code Online (Sandbox Code Playgroud)

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

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

您可以像这样使用宏:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);
Run Code Online (Sandbox Code Playgroud)

如果需要执行消息的结果,可以执行以下操作:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);
Run Code Online (Sandbox Code Playgroud)

  • @Eric不,它不能,除非你调用有趣的方法,如"initSomething"或"newSomething"或"somethingCopy". (4认同)
  • @Julian这确实有效,但是会关闭整个文件的警告 - 你可能不需要或不想要它.使用`pop`和`push`-pragma来包装它更清晰,更安全. (3认同)
  • 所有这一切都是它使编译器沉默.这并没有解决问题.如果选择器不存在,那你几乎搞砸了. (2认同)
  • 只有在使用`if([_target respondsToSelector:_selector]){`或类似逻辑包装时才应该使用它. (2认同)

ser*_*gio 209

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

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

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

  • 所以我在他们的论坛上得到苹果公司的确认,确实如此.他们将添加一个被遗忘的覆盖,以允许人们在将来的版本中禁用此警告.谢谢. (84认同)
  • ARC在编译时自动添加保留和发布的过程.它不是垃圾收集(这就是为什么它如此快速和低开销).它根本不是任意的.默认规则基于成熟的ObjC约定,这些约定已经持续应用了数十年.这避免了向每个解释其内存管理的方法显式添加`__attribute`的需要.但它也使编制者无法正确处理这种模式(这种模式曾经非常普遍,但近年来已被更强大的模式所取代). (8认同)
  • 所以我们不能再使用类型为"SEL"的ivar并根据具体情况分配不同的选择器?方式去,动态语言...... (8认同)
  • 谢谢你的答案,我会更多地了解这一点,看看发生了什么.有关如何绕过警告并使其消失的任何想法?我不愿意在我的代码中永远保留警告,以确保安全通话. (5认同)
  • 这个答案提出了一些问题,比如ARC试图根据约定和方法名称确定何时释放某些内容,那么它是如何"引用计数"的呢?如果ARC假设代码遵循某种约定而不是实际跟踪引用而不管遵循什么约定,那么您描述的行为听起来只比完全随意更好. (5认同)

0xc*_*ced 121

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

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

  • 请注意,您可以为特定文件而不是整个项目添加相同的标志.如果您查看Build Phases-> Compile Sources,您可以设置每个文件Compiler Flags(就像您想要从ARC中排除文件一样).在我的项目中,只有一个文件应该以这种方式使用选择器,所以我只是将其排除并留下其他文件. (12认同)

jlu*_*yiv 111

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

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
Run Code Online (Sandbox Code Playgroud)

代替

[_controller performSelector:NSSelectorFromString(@"someMethod")];
Run Code Online (Sandbox Code Playgroud)

你必须这样做

#import <objc/message.h>
Run Code Online (Sandbox Code Playgroud)

  • 不要这样做,`[__controller performSelector:NSSelectorFromString(@"someMethod")];`和`objc_msgSend(_controller,NSSelectorFromString(@"someMethod"));`不等同!看一下[Method Signature Mismatches](http://www.mikeash.com/pyblog/friday-qa-2011-08-05-method-signature-mismatches.html)和[Objective-C弱势的一大弱点]打字](http://cocoawithlove.com/2011/06/big-weakness-of-objective-c-weak-typing.html)他们正在深入解释这个问题. (21认同)
  • ARC承认Cocoa约定,然后根据这些约定添加保留和发布.由于C不遵循这些约定,因此ARC会强制您使用手动内存管理技术.如果您创建CF对象,则必须CFRelease()它.如果你dispatch_queue_create(),你必须dispatch_release().最重要的是,如果要避免ARC警告,可以通过使用C对象和手动内存管理来避免它们.此外,您可以通过在该文件上使用-fno-objc-arc编译器标志来基于每个文件禁用ARC. (8认同)
  • 不是没有铸造,你不能.Varargs与显式类型的参数列表不同.它通常是巧合的,但我并不认为"巧合"是正确的. (8认同)
  • @ 0xced在这种情况下,没关系.objc_msgSend不会为在performSelector中正常工作的任何选择器或其变体创建方法签名不匹配,因为它们只将对象作为参数.只要你的所有参数都是指针(包括对象),双精度和NSInteger/long,并且你的返回类型是void,pointer或long,那么objc_msgSend将正常工作. (5认同)

Bar*_*ker 88

要仅使用执行选择器在文件中忽略错误,请按如下方式添加#pragma:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
Run Code Online (Sandbox Code Playgroud)

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

  • 我知道你也可以在有问题的方法之后立即用"#pragma clang diagnostic warning"-Warc-performSelector-leaks"`重新发出警告.我知道如果我关掉一个警告,我想尽快把它重新打开,所以我不小心让另一个意外的警告失误.这不太可能是一个问题,但每当我关闭警告时,这只是我的做法. (6认同)
  • 您还可以在进行任何更改之前使用`#pragma clang diagnostic warning push`恢复以前的编译器配置状态,并使用`#pragma clang diagnostic warning pop`恢复以前的状态.如果要关闭负载并且不希望在代码中重新启用pragma行,则非常有用. (2认同)

mat*_*att 70

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

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];
Run Code Online (Sandbox Code Playgroud)

这会删除警告,大概是因为它让编译器放心不能返回任何对象并且以某种方式管理不当.

  • @Florian当然不一样!阅读我的回答:我说*如果*可以接受,因为结果是无效的并且runloop循环.那是我答案的第一句*. (10认同)
  • 你知道这是否真的解决了相关的内存管理问题,或者它是否有同样的问题但 Xcode 不够聪明,无法用这段代码警告你? (3认同)

syv*_*vex 34

这是基于上面给出的答案的更新宏.这个应该允许你使用return语句包装你的代码.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);
Run Code Online (Sandbox Code Playgroud)

  • `return`不必在宏内; `返回SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_ target performSelector:_action withObject:self]);`也有效,看起来更健全. (5认同)

Ben*_*hen 31

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

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

NSInvocation允许设置多个参数,因此不同于performSelector任何方法.

  • 你知道这是否真的解决了相关的内存管理问题,或者它是否有相同的问题,但Xcode不够聪明,不能用这个代码警告你? (3认同)

Chr*_*nce 20

好吧,这里有很多答案,但由于这有点不同,结合几个答案,我以为我会把它放进去.我正在使用一个NSObject类别,它检查以确保选择器返回void,并且还禁止编译器警告.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end
Run Code Online (Sandbox Code Playgroud)


Pat*_*ini 16

为了后人的缘故,我决定把帽子扔进戒指:)

最近我看到越来越多的重组远离target/ selector范式,支持协议,块等等.然而,有一个替代品performSelector我已经使用了几次:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];
Run Code Online (Sandbox Code Playgroud)

这些似乎是一个干净的,ARC安全的,几乎相同的替代品,performSelector无需太多objc_msgSend().

虽然,我不知道iOS上是否有模拟器.

  • 感谢包括这个..它在iOS中可用:`[[UIApplication sharedApplication] sendAction:to:from:forEvent:]`.我曾经调查过一次,但是在你的域或服务中间使用与UI相关的类只是为了进行动态调用有点尴尬..感谢包括这个! (6认同)
  • EW!它会有更多的开销(因为它需要检查方法是否可用并且如果不是则走上响应链)并且具有不同的错误行为(走上响应链并且如果找不到任何东西则返回NO)它响应方法,而不是简单地崩溃).当你想要`-performSelector:``中的`id`时,它也不起作用 (2认同)
  • @tc.除非`to:`为nil,否则它不会"走上响应者链".它只是直接进入目标对象,事先没有检查.所以没有"更多的开销".这不是一个很好的解决方案,但你给出的理由不是原因.:) (2认同)

c r*_*ald 15

Matt Galloway在这个帖子上的答案解释了原因:

考虑以下:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];
Run Code Online (Sandbox Code Playgroud)

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

如果忽略返回值,通常可以安全地抑制警告.如果你真的需要从performSelector获取一个保留对象,我不确定最佳实践是什么 - 除了"不要那样做".


Pav*_*pov 14

@ c-road在这里提供了与问题描述的正确链接.下面你可以看到我的例子,当performSelector导致内存泄漏时.

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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


Ben*_*ynn 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")
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )
Run Code Online (Sandbox Code Playgroud)


Swi*_*ect 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]];
Run Code Online (Sandbox Code Playgroud)

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

(参见回复)
使用任何线程代替[NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];
Run Code Online (Sandbox Code Playgroud)

危险的路线

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

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
Run Code Online (Sandbox Code Playgroud)

  • 措辞非常错误.安全路线并不比危险更安全.它可能更危险,因为它隐含地隐藏了警告. (3认同)