iOS - How to implement a performSelector with multiple arguments and with afterDelay?

IphoneIosSelectorPerformselector

Iphone Problem Overview


I am an iOS newbie. I have a selector method as follows -

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

}

I am trying to implement something like this -

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

But that gives me an error saying -

Instance method -performSelector:withObject:withObject:afterDelay: not found

Any ideas as to what I am missing?

Iphone Solutions


Solution 1 - Iphone

Personally, I think that a closer solution to your needs is the use of NSInvocation.

Something like the following will do the work:

indexPath and dataSource are two instance variables defined in the same method.

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

Solution 2 - Iphone

Because there is no such thing as a [NSObject performSelector:withObject:withObject:afterDelay:] method.

You need to encapsulate the data you want to send along into some single Objective C object (e.g. a NSArray, a NSDictionary, some custom Objective C type) and then pass it through the[NSObject performSelector:withObject:afterDelay:] method that is well known and loved.

For example:

NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

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

Solution 3 - Iphone

You can package your parameters into one object and use a helper method to call your original method as Michael, and others now, have suggested.

Another option is dispatch_after, which will take a block and enqueue it at a certain time.

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

});

Or, as you've already discovered, if you don't require the delay you can just use - performSelector:withObject:withObject:

Solution 4 - Iphone

The simplest option is to modify your method to take a single parameter containing both arguments, such as an NSArray or NSDictionary (or add a second method that takes a single parameter, unpacks it, and calls the first method, and then call the second method on a delay).

For instance, you could have something like:

- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

And then to call it, you can do:

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

Solution 5 - Iphone

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


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

}

and call it with:

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

Solution 6 - Iphone

You can find all the types of provided performSelector: methods here:

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

There are a bunch of variations but there isn't a version that takes multiple objects as well as a delay. You'll need to wrap up your arguments in an NSArray or NSDictionary instead.

- 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: 

Solution 7 - Iphone

I dislike the NSInvocation way, too complex. Let’s keep it simple and clean:

// 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);

Solution 8 - Iphone

I just did some swizzling and needed to call the original method. What I did was making a protocol and cast my object to it. Another way is to define the method in a category, but would need suppression of a warning (#pragma clang diagnostic ignored "-Wincomplete-implementation").

Solution 9 - Iphone

A simple and reusable way is to extend NSObject and implement

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

something like:

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

Solution 10 - Iphone

I would just create a custom object holding all my parameters as properties, and then use that single object as the parameter

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSuchiView Question on Stackoverflow
Solution 1 - IphonevalvolineView Answer on Stackoverflow
Solution 2 - IphoneMichael DautermannView Answer on Stackoverflow
Solution 3 - IphoneFiroze LafeerView Answer on Stackoverflow
Solution 4 - IphonearothView Answer on Stackoverflow
Solution 5 - IphoneMike WallaceView Answer on Stackoverflow
Solution 6 - IphoneStilesCrisisView Answer on Stackoverflow
Solution 7 - IphoneBB9zView Answer on Stackoverflow
Solution 8 - IphonedarkfaderView Answer on Stackoverflow
Solution 9 - IphoneKappeView Answer on Stackoverflow
Solution 10 - IphoneMobileMonView Answer on Stackoverflow