ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

这就是我正在做的事情:

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

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

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

w
wbyoung

解决方案

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

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<SomeProto>,则很有用)。这些函数指针称为 IMP,是简单的 typedef 函数指针 (id (*IMP)(id, SEL, ...))1。这可能接近方法的实际方法签名,但并不总是完全匹配。

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

最后,调用函数 pointer2。

复杂示例

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;

警告的原因

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

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

忽略非对象类型(void、int 等) 保留对象值,然后在不再使用时释放(标准假设) 在不再使用时释放新对象值(init/copy 系列中的方法或带有 ns_returns_retained 的属性)什么都没有&假设返回的对象值将在本地范围内有效(直到最里面的释放池被耗尽,归因于 ns_returns_autoreleased)

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

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

附加参数

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

静态选择器

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

[_controller performSelector:@selector(someMethod)];

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

抑制

在尝试考虑抑制此警告是必要的和良好的代码设计的情况时,我会一头雾水。如果有人有过需要消除此警告的经验(并且上述内容无法正确处理),请分享。

更多的

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

历史

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

随着 Swift 的引入,Apple has documented performSelector: 系列方法“本质上是不安全的”,它们不适用于 Swift。

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

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

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

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

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

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

4 有关详细信息,请参阅 retained return valuesunretained return values 上的 ARC 参考。


@wbyoung 如果您的代码解决了保留问题,我想知道为什么 performSelector: 方法没有以这种方式实现。它们具有严格的方法签名(返回 id,取一或两个 id),因此不需要处理原始类型。
@Andy 参数是根据方法原型的定义处理的(它不会被保留/释放)。关注点主要基于返回类型。
使用最新的 Xcode 时,“复杂示例”会给出错误 Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'。 (5.1.1) 尽管如此,我还是学到了很多!
void (*func)(id, SEL) = (void *)imp; 无法编译,我已将其替换为 void (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp; 更改为 <…> = (void (*))imp;<…> = (void (*) (id, SEL))imp;
S
Scott Thompson

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

当优化设置为 None 以外的任何值时,此方法可能会导致内存泄漏。
@Eric 不,它不能,除非您正在调用诸如“initSomething”或“newSomething”或“somethingCopy”之类的有趣方法。
@Julian 这确实有效,但是会关闭整个文件的警告——你可能不需要或不想要那个。用 poppush-pragma 包装它更干净、更安全。
所有这一切都是它使编译器静音。这并不能解决问题。如果选择器不存在,你就完蛋了。
这仅应在被 if ([_target respondsToSelector:_selector]) { 或类似逻辑包装时使用。
b
brainjam

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

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

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


感谢您的回答,我将对此进行更多研究以了解发生了什么。关于如何绕过警告并使其消失的任何想法?我不想让警告永远存在于我的代码中,因为这是一个安全的调用。
所以我在他们的论坛上得到了 Apple 某人的确认,确实是这样。他们将添加一个被遗忘的覆盖,以允许人们在未来的版本中禁用此警告。谢谢。
这个答案提出了一些问题,比如如果 ARC 试图根据约定和方法名称来决定何时发布某些东西,那么它是如何“引用计数”的?如果 ARC 假设代码遵循某种约定,而不是不管遵循什么约定,实际跟踪引用,那么您描述的行为听起来只比完全任意的好一点。
ARC 在编译时自动执行添加保留和发布的过程。它不是垃圾收集(这就是为什么它如此快速和低开销的原因)。这根本不是任意的。默认规则基于几十年来一直应用的成熟的 ObjC 约定。这避免了在解释其内存管理的每个方法中显式添加 __attribute 的需要。但这也使得编译器无法正确处理这种模式(这种模式曾经非常普遍,但近年来已被更强大的模式所取代)。
所以我们不能再拥有 SEL 类型的 ivar 并根据情况分配不同的选择器了吗?走的路,动态语言...
0
0xced

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

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


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

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

你需要标题:

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

ARC 识别 Cocoa 约定,然后根据这些约定添加保留和发布。因为 C 不遵循这些约定,ARC 强制您使用手动内存管理技术。如果你创建一个 CF 对象,你必须 CFRelease() 它。如果你 dispatch_queue_create(),你必须 dispatch_release()。底线,如果你想避免 ARC 警告,你可以通过使用 C 对象和手动内存管理来避免它们。此外,您可以通过在该文件上使用 -fno-objc-arc 编译器标志在每个文件的基础上禁用 ARC。
不是没有铸造,你不能。 Varargs 与显式类型的参数列表不同。它通常会巧合,但我不认为“巧合”是正确的。
不要那样做,[_controller performSelector:NSSelectorFromString(@"someMethod")];objc_msgSend(_controller, NSSelectorFromString(@"someMethod")); 不等价!看看 Method Signature MismatchesA big weakness in Objective-C's weak typing 他们正在深入解释问题。
@0xced 在这种情况下,没关系。 objc_msgSend 不会为任何可以在 performSelector: 或其变体中正常工作的选择器创建方法签名不匹配,因为它们只将对象作为参数。只要您的所有参数都是指针(包括对象)、双精度数和 NSInteger/long,并且您的返回类型是 void、指针或 long,那么 objc_msgSend 就可以正常工作。
ObjC 没有像 c++ 那样的函数重载。因此,即使认为 mikeash 的网站表达了真正的担忧,当您尝试重载由于 ObjC 而无法重载的方法时(不意味着覆盖 - 以防有人混用这些词),您应该得到编译器警告。
B
Barlow Tucker

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

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

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


我认为您也可以在使用 #pragma clang diagnostic warning "-Warc-performSelector-leaks" 的相关方法之后立即重新打开警告。我知道如果我关闭警告,我喜欢尽快将其重新打开,所以我不会不小心让另一个意料之外的警告溜走。这不太可能是一个问题,但这只是我关闭警告时的习惯。
您还可以在进行任何更改之前使用 #pragma clang diagnostic warning push 恢复以前的编译器配置状态,并使用 #pragma clang diagnostic warning pop 恢复以前的状态。如果您要关闭负载并且不想在代码中包含大量重新启用的编译指示行,这很有用。
它只会忽略以下行?
m
matt

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

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

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


你知道这是否真的解决了相关的内存管理问题,还是有同样的问题,但 Xcode 不够聪明,无法用这段代码警告你?
这在语义上不是一回事!使用 performSelector:withObject:AfterDelay: 将在 runloop 的下一次运行中执行选择器。因此,此方法立即返回。
@Florian 当然不一样!阅读我的回答:我说如果可以接受,因为结果是无效的并且运行循环会循环。这是我回答的第一句话。
s
syvex

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

return 不必在宏内部; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]); 也有效,看起来更健康。
B
Benedict Cohen

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

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 不同,它适用于任何方法。


你知道这是否真的解决了相关的内存管理问题,还是有同样的问题,但 Xcode 不够聪明,无法用这段代码警告你?
你可以说它解决了内存管理问题;但这是因为它基本上可以让您指定行为。例如,您可以选择让调用保留或不保留参数。据我目前所知,它试图通过相信您知道自己在做什么并且不向其提供不正确的数据来解决可能出现的签名不匹配问题。我不确定是否可以在运行时执行所有检查。正如另一条评论中提到的,mikeash.com/pyblog/… 很好地解释了不匹配可以做什么。
C
Chris Prince

好吧,这里有很多答案,但是由于这有点不同,所以结合了一些我认为我会放入的答案。我正在使用一个 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

'v' 应该被 _C_VOID 替换吗? _C_VOID 在 中声明。
P
Patrick Perini

为了子孙后代,我决定把我的帽子扔进戒指:)

最近,我看到越来越多的重组从 target/selector 范式转向协议、块等。但是,我有一个替代 performSelector 的替代品现在已经用过几次了:

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

这些似乎是对 performSelector 的干净、ARC 安全且几乎相同的替代品,而无需对 objc_msgSend() 进行过多处理。

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


感谢您提供此内容。它在 iOS 中可用:[[UIApplication sharedApplication] sendAction: to: from: forEvent:]。我看过一次,但是在你的域或服务中间使用与 UI 相关的类只是为了进行动态调用有点尴尬。谢谢你包括这个!
哇!它将有更多的开销(因为它需要检查该方法是否可用,如果不可用,则沿着响应者链向上走)并且具有不同的错误行为(沿着响应者链向上走,如果找不到任何东西,则返回 NO它响应方法,而不是简单地崩溃)。当您想要来自 -performSelector:...id 时,它也不起作用
@tc。除非 to: 为 nil,否则它不会“沿着响应者链向上走”,但事实并非如此。它只是直接到达目标对象而无需事先检查。所以没有“更多的开销”。这不是一个很好的解决方案,但你给出的理由不是理由。 :)
C
Community

Matt Galloway 在 this thread 上的回答解释了原因:

考虑以下内容: id anotherObject1 = [someObject performSelector:@selector(copy)]; id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];现在,ARC 怎么知道第一个返回一个保留计数为 1 的对象,而第二个返回一个自动释放的对象?

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


C
Community

@c-road 提供了问题描述 here 的正确链接。下面你可以看到我的示例,当 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 返回保留对象。

https://i.stack.imgur.com/iW1Po.png


B
Ben Flynn

为了使 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,我没有添加宏。有人在我的回复中添加了这一点。就个人而言,我不会使用宏。 pragma 用于解决代码中的特殊情况,并且 pragma 非常明确和直接地说明正在发生的事情。我更喜欢将它们保留在适当的位置,而不是隐藏或将它们抽象到宏后面,但这只是我。 YMMV。
@ScottThompson 这很公平。对我来说,在我的代码库中搜索这个宏很容易,而且我通常还会添加一个非静音警告来处理潜在问题。
C
Community

不要压制警告!

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

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

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

措辞非常错误。安全路线根本不比危险更安全。可以说它更危险,因为它隐含地隐藏了警告。
我会把措辞改成不是侮辱性的,但我信守诺言。我唯一可以接受的静音警告是如果我不拥有代码。没有工程师可以在不了解所有后果的情况下安全地维护沉默的代码,这意味着阅读这个论点,这种做法是有风险的;特别是如果您考虑 12 种简单的英语,强大的替代方案。
不,你没有明白我的意思。使用 performSelectorOnMainThread不是消除警告的好方法,它有副作用。 (它不能解决内存泄漏)额外的 #clang diagnostic ignored 以非常清晰的方式显式抑制警告。
确实,在非 - (void) 方法上执行选择器是真正的问题。
以及如何通过此调用具有多个参数的选择器并同时保持安全? @SwiftArchitect
h
honus

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


实际上,块很容易意外创建 ARC 无法解决的保留循环。我仍然希望当您通过 ivar 隐式使用 self 时出现编译器警告(例如 ivar 而不是 self->ivar)。
你的意思是像 -Wimplicit-retain-self ?
s
supersabbath

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

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

a
arsenius

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

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

D
Damon

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

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

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

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

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

[self.source doSomethingWithObject:object];

嘿吴,谢谢,但使用 NSSelectorFromString 的重点是当您不知道要在运行时调用哪个选择器时。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅