39

I'm trying to call a method after some delay.

I know there is a solution for that:

[self performSelector:@selector(myMethod) withObject:nil afterDelay:delay];

I saw this question and Documentation

But my question is: How can I call a method that takes two parameters??

for instance:

- (void) MoveSomethigFrom:(id)from To:(id)to;

How would I call this method with delay, using performSelector:withObject:afterDelay:

Thanks

Community
  • 1
  • 1
Frade
  • 2,780
  • 4
  • 25
  • 37

6 Answers6

112

use dispatch_after:

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    //code to be executed on the main queue after delay
    [self MoveSomethingFrom:from To:to];
});

EDIT 2015: For Swift, i recommend using this small helper method: dispatch_after - GCD in swift?

Community
  • 1
  • 1
Martin Ullrich
  • 78,211
  • 20
  • 211
  • 189
  • 2
    Either that, or you can put as many parameters as you want in a container and send it as an argument for the `performSelector: withObject: afterDelay` method. – Alexander Mar 09 '12 at 13:31
  • This seems a good approach, it really delays the call. But it happens something strange, the method doesn't do what is expected, does something else, that I can't even understand.. – Frade Mar 09 '12 at 15:21
  • got it.. It was some code that was executed during the delay.. thanks – Frade Mar 09 '12 at 15:35
  • is there a way to cancel scheduled call? – vir us Aug 27 '14 at 20:49
  • if someone is interested this should work to cancel scheduled call (if performSelector:withObject:afterDelay: was used): [NSObject cancelPreviousPerformRequestsWithTarget:yourTarget selector:aSelector object: anArgument]; – vir us Aug 27 '14 at 21:13
  • @iEngineer: Sure u got everything right? i once used "MSEC_PER_SEC" instead of "NSEC_PER_SEC" which is 10^6 times faster. – Martin Ullrich Feb 08 '16 at 18:26
7

You can also implement method in NSObject's category using NSInvocation object (works in all versions of iOS). I guess it should be something like this:

@interface NSObject(DelayedPerform)

- (void)performSelector:(SEL)aSelector withObject:(id)argument0 withObject:(id)argument1  afterDelay:(NSTimeInterval)delay {

  NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];

  NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  [invocation setTarget:self];
  [invocation setSelector:aSelector];
  [invocation setArgument:&argument0 atIndex:2];
  [invocation setArgument:&argument1 atIndex:3];

  [invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay];

}

@end
Eldar Markov
  • 940
  • 9
  • 13
  • you can use it in >= 3.2 as well. I just wanted to show that it's not necessary to use GCD. I'll edit my original answer) – Eldar Markov Mar 09 '12 at 14:38
  • Ok.. I'm sorry but, what is GCD? what's the problem using it? – Frade Mar 09 '12 at 14:46
  • I'm asking, because I have three answers and the 3 are good. Just trying to understand which is the best one to use. Thanks – Frade Mar 09 '12 at 14:48
  • 1
    GCD - it's Grand Central Dispatch. Part of iOS sdk 4.0 and higher.If you want to use it in all versions of iOS use mine version) – Eldar Markov Mar 09 '12 at 14:53
  • Yes if you really want to deploy on devices < 4.0 you have to stay away from GCD. But developing for iOS 4+ is safe as over 90% of active devices are running it and testing/coding effort is heavily reduced (assuming that you do serious testing) – Martin Ullrich Mar 09 '12 at 15:47
  • additionally, GCD improves code readability because because you don't have to have lots of methods being called asynchronously (you "just" have to get used to ^{}-Notations) – Martin Ullrich Mar 09 '12 at 15:48
6

Other ideas:

1) You could use NSInvocations:

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)signature
(>> see Eldar Markov's answer)

Documentation:
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html

2) You could use a helper method..

[self performSelector:@selector(helperMethod) withObject:nil afterDelay:delay];

- (void) helperMethod
{
    // of course x1 and x2 have to be safed somewhere else
    [object moveSomethigFrom: x1 to: x2];
}

3) You could use an array or a dictionary as parameter..

NSArray* array = [NSArray arrayWithObjects: x1, x2, nil];
[self performSelector:@selector(handleArray:) withObject:array afterDelay:delay];

- (void) handleArray: (NSArray*) array
{
    [object moveSomethigFrom: [array objectAtIndex: 0] to: [array objectAtIndex: 1]];
}
Community
  • 1
  • 1
calimarkus
  • 9,738
  • 1
  • 24
  • 47
  • As a suggestion, don't reference other answers on SO as being "above" or "below", as they can be sorted more ways than one, and that most people sort by rating, which can be volatile. I suggest hitting the "share" button beneath the answer and sharing a link to it, for incase a question ends up with a large number of answers. – ArtOfWarfare May 12 '13 at 14:51
2

Swift:

    let delayInSeconds = 3.0;
    let delay = delayInSeconds * Double(NSEC_PER_SEC)
    let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay));
    dispatch_after(popTime, dispatch_get_main_queue(), {
        // DO SOMETHING AFTER 3 sec
    });
Alexander Volkov
  • 6,770
  • 1
  • 41
  • 37
1

Here is how you can trigger a block after a delay in Swift:

runThisAfterDelay(seconds: 5) { () -> () in
    print("Prints this 5 seconds later in main queue")
    //Or perform your selector here
}

/// EZSwiftExtensions
func runThisAfterDelay(seconds seconds: Double, after: () -> ()) {
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
    dispatch_after(time, dispatch_get_main_queue(), after)
}

The argument count does not really matter.

Its included as a standard function in my repo: https://github.com/goktugyil/EZSwiftExtensions

Esqarrouth
  • 35,175
  • 17
  • 147
  • 154
0

These will all work, but are all much more complex than is needed.

Design the method to be called with an NSDictionary argument. Put the objects in it you need.

If you want the method to be accessible by other means as well, call instead a method that 'unwraps' the dictionary and calls the intended method with explicit parameters.

tooluser
  • 1,418
  • 14
  • 21