使用-performSelector:和直接调用方法有何区别?

117

我在学习Objective-C,想知道下面两个语句的区别:

[object performSelector:@selector(doSomething)]; 

[object doSomething];
5个回答

194

performSelector基本上允许您动态确定要在给定对象上调用哪个选择器。换句话说,选择器不需要在运行时之前确定。

因此,即使这些是等效的:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 
第二种形式允许您这样做:
SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

在发送消息之前。


3
值得指出的是,你实际上会将findTheAppropriateSelectorForTheCurrentSituation()方法返回的结果分配给aSelector变量,然后调用[anObject performSelector:aSelector]方法。@selector产生一个SEL类型的值。 - Daniel Yankowsky
4
使用 performSelector: 是你只有在类中实现目标-动作时才会用到的东西。而 performSelectorInBackground:withObject:performSelectorOnMainThread:withObject:waitUntilDone: 这两个方法则更加常用。前者用于创建后台线程,后者则用于在后台线程中回调结果到主线程。 - PeyloW
2
performSelector 还可以用来抑制编译警告。如果你知道方法存在(例如使用 respondsToSelector 后),它将阻止 Xcode 显示“可能无法响应 your_selector”的消息。但是不要仅仅使用它来代替查找警告的真正原因。 ;) - Marc
我在StackOverflow的另一个帖子上读到,使用performSelector方法反映出设计极其糟糕,而这个观点得到了大量支持。但我现在很想再找到它。我使用谷歌搜索,限制结果为stackoverflow,但返回了18000多条结果。太恶心了。 - Logicsaurus Rex
“糟糕设计的反射”过于简单化了。这是在块可用之前的情况,而且并非所有的使用都是不好的,当时和现在都是如此。虽然现在块已经可用,但除非你正在做某些非常简单的事情,否则对于新代码来说,那可能是更好的选择。” - Ethan
显示剩余2条评论

17

对于这个问题中的非常基本的示例,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

将要发生的事情并没有什么区别。 doSomething方法将由对象同步执行。 只是“doSomething”是一个非常简单的方法,不返回任何内容,也不需要任何参数。

如果这是一些更复杂的内容,比如:

(void)doSomethingWithMyAge:(NSUInteger)age;

如果使用“performSelector”任何变种,调用[object doSomethingWithMyAge:42]就会变得复杂,因为只有带对象参数的变种才能接受参数。 这里的选择器将是“doSomethingWithMyAge:”,但任何尝试

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

代码无法编译。即使传递NSNumber:@(42)而不是42也无济于事,因为该方法期望基本的C类型 - 而不是对象。

此外,performSelector变体最多有2个参数,没有更多了。而方法很多次有更多的参数。

我发现尽管performSelector有同步变体:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

始终返回一个对象,但我也能够返回一个简单的BOOL或NSUInteger,并且它也可以工作。

performSelector的两个主要用途之一是动态组合您想要执行的方法的名称,如以前的答案中所解释的那样。例如:

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

另一种用法是异步地向对象分派消息,稍后在当前运行循环中执行。为此,有几种其他的performSelector变体。

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(是的,我从几个Foundation类别中收集了它们,比如NSThread、NSRunLoop和NSObject)

每个变量都有自己特殊的行为,但所有变量都有一个共同点(至少在waitUntilDone设置为NO时)。"performSelector"调用会立即返回,在一段时间后,消息才会被放置在当前运行循环中。

由于延迟执行 - 自然地,方法的选择器中没有可用的返回值,因此所有这些异步变量都具有-(void)返回值。

希望我在某种程度上涵盖了这个问题...


12

5
要实现这个,你需要使用一个 NSInvocation 对象。 - Dave DeLong
6
另一个区别是:performSelector:及其相关方法都需要传递对象参数,这意味着你不能使用它们来调用(例如)setAlphaValue:,因为它的参数是一个浮点数。 - Chuck

4
选择器有点像其他语言中的函数指针。当您在编译时不知道要在运行时调用哪个方法时,可以使用它们。与函数指针一样,它们仅封装调用的动作部分。如果该方法具有参数,则还需要传递它们。
NSInvocation具有类似的功能,但它绑定了更多的信息。它不仅包括动作部分,还包括目标对象和参数。当您想要在将来调用特定对象的特定方法并传递特定参数时,这非常有用。您可以构建一个适当的NSInvocation,并稍后触发它。

5
选择器(Selectors)和函数指针并不完全相同。函数指针可以带参数调用,而选择器则用于调用任何实现了该方法的对象上的特定方法;与函数指针不同,选择器没有完整的调用上下文。 - bbum
1
选择器不同于函数指针,但我仍认为它们相似。它们代表动作。C语言函数指针也代表动作。没有额外上下文,两者都没有用处。选择器需要对象和参数; 函数指针需要参数(其中可能包括要操作的对象)。我的观点是强调它们与NSInvocation对象不同,因为后者包含了所有必要的上下文。也许我的比较有些困惑,如果是这样,我很抱歉。 - Daniel Yankowsky
1
选择器不是函数指针。它们实际上只是简单的C字符串,包含一个方法的“名称”(而不是“函数”)。它们甚至不是方法签名,因为它们不包含参数类型。一个对象可以有多个使用相同选择器的方法(具有不同的参数类型或返回类型)。 - Motti Shneor

-8

这两者之间还有一个微妙的区别。

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

这是苹果文档的摘录 “performSelector:withObject:afterDelay: 在下一个运行循环周期内,在可选延迟期之后,在当前线程上执行指定的选择器。因为它等待到下一个运行循环周期才执行选择器,所以这些方法从当前正在执行的代码提供了自动小延迟。多个排队的选择器按它们被排队的顺序依次执行。”

2
你的回答事实上是不正确的。你引用的文档是关于 performSelector:withObject:afterDelay: 的,但问题和你所提供的代码片段使用的是 performSelector:,这是一个完全不同的方法。根据它的文档:<quote>performSelector: 方法相当于直接向接收者发送 aSelector 消息。</quote> - jscs
3
感谢 Josh 的澄清。你是正确的,我以为 performSelector/performSelector:withObject/performSelector:withObject:afterDelay 这些方法的行为都相同,这是我的错误。 - avi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接