iOS-如何使用多个参数和afterDelay实现performSelector?


90

我是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

关于我所缺少的任何想法吗?

Answers:


142

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

类似于以下内容的工作:

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

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 2014年

这看起来是一个很好的解决方案。有没有办法从使用该技术调用的方法中获取返回值?
David Pettigrew 2014年

15
您如何使用这种技术指定延迟?
death_au 2014年

4
@death_au,而不是invoke致电: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; 我必须同意这是一个很好的解决方案。祝大家编码愉快!
Maxim Chetrusca 2014年

2
谈话有点晚,但是有一个问题。什么是dropDownDelegate?
蔬菜通心粉汤

98

因为没有这样的东西 [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 2011年

如何在此处传递布尔型?
virata

使它成为Objective C风格的对象(例如“ NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];”),并将其作为一个参数@virata传递。
Michael Dautermann

2
这是一个单独的问题@Raj ...请单独发布。
Michael Dautermann 2012年

34

您可以将参数打包到一个对象中,并使用一个助手方法来调用您的原始方法,就像迈克尔和其他人建议的那样。

另一个选项是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用来给假装定时器仅返回自身的弱链接-因此,您不会最终人为地延长对象的生命周期,例如,如果performSelector:afterDelay:产生类似于尾巴的效果递归(尽管没有递归),然后它解决了保留周期。
汤米

1
是的,这应该是公认的答案。它更合适,更直接。
Roohul

7

最简单的选择是将方法修改为采用包含两个参数的单个参数,例如NSArrayor或NSDictionary(或添加采用单个参数的第二个方法,将其解压缩,然后调用第一个方法,然后调用第二个方法上的方法延迟)。

例如,您可能会遇到以下情况:

- (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 2015年

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”
安东

1

我只是做了一些麻烦,需要调用原始方法。我所做的就是制定一个协议,并将我的对象投向它。另一种方法是在类别中定义方法,但需要禁止显示警告(#pragma clang诊断忽略“ -Wincomplete-implementation”)。


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

0

我只是创建一个将所有参数都保存为属性的自定义对象,然后将该单个对象用作参数

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.