88

I need to add a delay between the execution of two lines in a(same) function. Is there any favorable option to do this?

Note: I don't need two different functions to do this, and the delay must not affect other functions' execution.

eg:

line 1: [executing first operation];

line 2: Delay                        /* I need to introduce delay here */

line 3: [executing second operation];
starball
  • 20,030
  • 7
  • 43
  • 238
Krishna Raj Salim
  • 7,331
  • 5
  • 34
  • 66

7 Answers7

236

You can use gcd to do this without having to create another method

// ObjC

NSTimeInterval delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  NSLog(@"Do some work");
});

// Swift

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("Do some work)
}

You should still ask yourself "do I really need to add a delay" as it can often complicate code and cause race conditions

Mark Bridges
  • 8,228
  • 4
  • 50
  • 65
Paul.s
  • 38,494
  • 5
  • 70
  • 88
  • 2
    It's correct to warn people about this but if you code controllers that need to wait for an external device with a fixed time of processing it is NOT non-sense. – user2161301 Oct 02 '15 at 11:55
  • 3
    What about delaying an HTTP request while writing in a search bar? Filtering results while typing? Waiting one second before requesting a filtered list instead of querying the server for every key the user taps doesn't sound like a nonsense to me. – Alejandro Iván Jul 15 '16 at 17:00
28

You can use the NSThread method:

[NSThread sleepForTimeInterval: delay];

However, if you do this on the main thread you'll block the app, so only do this on a background thread.


or in Swift

NSThread.sleepForTimeInterval(delay)

in Swift 3

Thread.sleep(forTimeInterval: delay)
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
24

This line calls the selector secondMethod after 3 seconds:

[self performSelector:@selector(secondMethod) withObject:nil afterDelay:3.0 ];

Use it on your second operation with your desired delay. If you have a lot of code, place it in its own method and call that method with performSelector:. It wont block the UI like sleep

Edit: If you do not want a second method you could add a category to be able to use blocks with performSelector:

@implementation NSObject (PerformBlockAfterDelay)

- (void)performBlock:(void (^)(void))block 
          afterDelay:(NSTimeInterval)delay
{
    block = [block copy];
    [self performSelector:@selector(fireBlockAfterDelay:) 
               withObject:block 
               afterDelay:delay];
}

- (void)fireBlockAfterDelay:(void (^)(void))block
{
    block();
}

@end

Or perhaps even cleaner:

void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void))
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay),
      dispatch_get_current_queue(), block);
}
Sunkas
  • 9,542
  • 6
  • 62
  • 102
  • 6
    The OP explicitly states they do not want a second method :S – Paul.s Mar 11 '13 at 10:10
  • He could add a category to be able to use blocks with `performSelector`: http://stackoverflow.com/questions/4007023/blocks-instead-of-performselectorwithobjectafterdelay – Sunkas Mar 11 '13 at 10:31
  • @Sunkas: Thanks for the answer. But I've already mentioned that I do not want to add a second function. – Krishna Raj Salim Mar 11 '13 at 12:09
  • 3
    Using that answer will give you a quite clean way of running code after a delay without a second method. `[self performBlock:^{ your_code } afterDelay:0.1];` – Sunkas Mar 25 '14 at 14:53
  • 4
    If the OP is unwilling to add another method they should explain why, because that's one very good way to use the tools as they are designed to work. Are methods bad? Are they paying by the (void)? – tooluser May 23 '14 at 03:59
8

I have a couple of turn-based games where I need the AI to pause before taking its turn (and between steps in its turn). I'm sure there are other, more useful, situations where a delay is the best solution. In Swift:

        let delay = 2.0 * Double(NSEC_PER_SEC) 
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) 
        dispatch_after(time, dispatch_get_main_queue()) { self.playerTapped(aiPlayView) }

I just came back here to see if the Objective-C calls were different.(I need to add this to that one, too.)

Marcello B.
  • 4,177
  • 11
  • 45
  • 65
David Reich
  • 709
  • 6
  • 14
7

[checked 27 Nov 2020 and confirmed to be still accurate with Xcode 12.1]

The most convenient way these days: Xcode provides a code snippet to do this where you just have to enter the delay value and the code you wish to run after the delay.

  1. click on the + button at the top right of Xcode.
  2. search for after
  3. It will return only 1 search result, which is the desired snippet (see screenshot). Double click it and you're good to go.

screenshot illustrating how to get the snippet from within Xcode itself

auspicious99
  • 3,902
  • 1
  • 44
  • 58
4

If you're targeting iOS 4.0+, you can do the following:

[executing first operation];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [executing second operation];
});
Attila H
  • 3,616
  • 2
  • 24
  • 37
  • 1
    You mean iOS 4? [Grand central dispatch Function Reference](http://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/doc/uid/TP40008079-CH2-SW3) – Paul.s Mar 11 '13 at 10:47
2

Like @Sunkas wrote, performSelector:withObject:afterDelay: is the pendant to the dispatch_after just that it is shorter and you have the normal objective-c syntax. If you need to pass arguments to the block you want to delay, you can just pass them through the parameter withObject and you will receive it in the selector you call:

[self performSelector:@selector(testStringMethod:) 
           withObject:@"Test Test" 
           afterDelay:0.5];

- (void)testStringMethod:(NSString *)string{
    NSLog(@"string  >>> %@", string);
}

If you still want to choose yourself if you execute it on the main thread or on the current thread, there are specific methods which allow you to specify this. Apples Documentation tells this:

If you want the message to be dequeued when the run loop is in a mode other than the default mode, use the performSelector:withObject:afterDelay:inModes: method instead. If you are not sure whether the current thread is the main thread, you can use the performSelectorOnMainThread:withObject:waitUntilDone: or performSelectorOnMainThread:withObject:waitUntilDone:modes: method to guarantee that your selector executes on the main thread. To cancel a queued message, use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget:selector:object: method.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74