iOS - 如何实现带有多个参数和延迟执行的performSelector方法?

91

我是一个iOS的新手。我有一个如下的选择器方法 -

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

我正在尝试实现类似这样的东西 -

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second" afterDelay:15.0];

但这会给我一个错误信息 -

Instance method -performSelector:withObject:withObject:afterDelay: not found
任何想法关于我漏掉了什么?
11个回答

146

就我个人而言,我认为更符合您需求的解决方案是使用 NSInvocation。

以下类似的代码可以完成工作:

indexPathdataSource 是在同一方法中定义的两个实例变量。

SEL aSelector = NSSelectorFromString(@"dropDownSelectedRow:withDataSource:");

if([dropDownDelegate respondsToSelector:aSelector]) {
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
    [inv setSelector:aSelector];
    [inv setTarget:dropDownDelegate];

    [inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
    [inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation

    [inv invoke];
}

2
同意。这应该是正确的答案。非常有帮助的解决方案。特别是在我的情况下,不允许更改包含多个参数的方法的签名。 - AbhijeetMishra
这看起来是一个很好的解决方案。使用这种技术调用方法时,有没有一种获取返回值的方式? - David Pettigrew
15
您如何使用这种技术指定延迟时间? - death_au
4
@death_au,只需要将“invoke”改为“performSelector:@selector(invoke) withObject:nil afterDelay:1”的调用即可。我必须承认这是一个很好的解决方案。祝大家编码愉快! - Maxim Chetrusca
2
有点晚加入讨论,但是我有个问题。dropDownDelegate是什么? - Minestrone-Soup
显示剩余2条评论

96
因为不存在这样的[NSObject performSelector:withObject:withObject:afterDelay:]方法。
您需要将要发送的数据封装到某个单一的Objective C对象中(例如一个NSArray,一个NSDictionary或一些自定义Objective C类型),然后通过广为人知和喜爱的[NSObject performSelector:withObject:afterDelay:]方法传递它。
例如:
NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];

如果我删除afterDelay参数,就不会出现错误。这是否意味着afterDelay不能与多个参数一起使用? - Suchi
1
你不会得到错误,但我敢打赌你在运行时会得到“选择器未找到”的异常(并且你尝试执行的操作将不会被调用)……试试看。 :-) - Michael Dautermann
有人能告诉我如何根据数组值动态更改标签文本吗? - Raj
2
那是一个分开的问题@Raj... 请单独发布。 - Michael Dautermann
@Suchi,针对您12月8日的问题,这是因为苹果提供了多种方法,包括performSelector:withObject:performSelector:withObject:withObject:performSelector:withObject:afterDelay:。移除afterDelay只是调用了另一个方法。 - Connor
显示剩余2条评论

34
你可以将你的参数打包成一个对象,并使用一个帮助方法来调用你原来的方法,就像Michael和其他人建议的那样。
另一个选择是dispatch_after,它会接收一个块并在特定时间将其排队。
double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [self fooFirstInput:first secondInput:second];

});

或者,正如您已经发现的那样,如果您不需要延迟,您可以直接使用- performSelector:withObject:withObject:


这种方法的好处是你可以使用__weak,只给你的模拟计时器一个弱引用返回到self - 这样你就不会人为地延长对象的生命周期。例如,如果你的performSelector:afterDelay:效果有点像尾递归(虽然没有递归),那么它就会解决保留循环问题。 - Tommy
1
是的,这应该是被接受的答案。它更加恰当和直接了当。 - Roohul

7
最简单的方法是修改您的方法,使其接受一个包含两个参数的单一参数,例如NSArrayNSDictionary(或者添加一个接受单一参数、解包它并调用第一个方法的第二个方法,然后在延迟后调用第二个方法)。
例如,您可以有以下内容:
- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

然后,你可以这样调用它:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];

如果方法无法修改,比如它存在于UIKit或其他地方怎么办?而且,将方法更改为使用NSDictionary也会失去类型安全性。这并不理想。 - fatuhoku
@fatuhoku - 这已经被括号所覆盖了; “添加第二个方法,它接受一个参数,对其进行解包并调用第一个方法”。这适用于第一个方法位于何处。至于类型安全性,自从决定使用performSelector:(或NSInvocation)以来,就已经失去了。如果这是一个问题,最好的选择可能是通过GCD进行。 - aroth

6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

并使用以下方式调用:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];

5

您可以在此处找到所有提供的performSelector:方法的类型:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

有很多变体,但没有一个版本可以同时接受多个对象和延迟。您需要将参数打包在NSArray或NSDictionary中。

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
 performSelector:withObject:afterDelay:
 performSelector:withObject:afterDelay:inModes:
 performSelectorOnMainThread:withObject:waitUntilDone:
 performSelectorOnMainThread:withObject:waitUntilDone:modes:
 performSelector:onThread:withObject:waitUntilDone:
 performSelector:onThread:withObject:waitUntilDone:modes:
 performSelectorInBackground:withObject: 

2
我不喜欢使用NSInvocation的方式,它太复杂了。让我们保持简单和清晰:
// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;

// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];

// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);

不错!将“vc”替换为“target”。 - Anton

1
我刚刚进行了一些重写操作并需要调用原始方法。我所做的是创建一个协议并将对象强制转换为该协议。 另一种方法是在类别中定义该方法,但需要抑制警告(#pragma clang diagnostic ignored“-Wincomplete-implementation”)。

0
我会创建一个自定义对象,将所有参数作为属性存储在其中,然后使用该单个对象作为参数。

0
一个简单且可重用的方法是扩展NSObject并实现

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;

类似这样:

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
    NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
    [invocation setSelector: aSelector];

    int index = 2; //0 and 1 reserved
    for (NSObject *argument in arguments) {
        [invocation setArgument: &argument atIndex: index];
        index ++;
    }
    [invocation invokeWithTarget: self];
}

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