12

I am trying to call a function after a delay. On iOS10 I am able to use Timer.scheduledTimer which does indeed call my closure after the given delay. However, on iOS9 I am using DispatchQueue.main.asyncAfter and it calls my closure with a six second delay.

The delay I am testing with is 60 seconds. Timer.scheduledTimer calls closure after 60 seconds, DispatchQueue.main.asyncAfter after 66 seconds. The six second delay is consequent, if I schedule two delays of 60 seconds the second delay is called after 132 seconds using DispatchQueue.main.asyncAfter.

func delay(delay:Double, closure:@escaping ()->()) {
    NSLog("Calling method with delay of: \(delay)")

    if #available(iOS 10.0, *) {
        Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { (timer) in
            closure()
        }
    } else {
        // TODO: Is not accurate, delay of +- 6 seconds
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            closure()
        }
    }
}

The code that calls the delay function:

func scheduleStudy(minutes: Int, secondsFromNow: Int) {
    // Study notification
    if #available(iOS 10.0, *) {
        self.addiOS10StudyNotification(minutes: minutes, secondsFromNow: secondsFromNow)
    }else{
        self.addiOS9StudyNotification(minutes: minutes, secondsFromNow: secondsFromNow)
    }

    // Study timer
    delay(delay: Double(secondsFromNow)) {
        self.onStudy(minutes: minutes)
    }

    NSLog("Scheduled study for \(minutes) minutes in \(secondsFromNow) seconds from now.")
}

Planning notifications and methods calls using Timer.scheduledTimer

2016-12-06 13:34:06.024714 Mattie[1386:360881] Calling method with delay of: 0.0
2016-12-06 13:34:06.025072 Mattie[1386:360881] Scheduled study for 1 minutes in 0 seconds from now.
2016-12-06 13:34:06.036953 Mattie[1386:360881] Calling method with delay of: 60.0
2016-12-06 13:34:06.037191 Mattie[1386:360881] Scheduled pause for 1 minutes in 60 seconds from now.
2016-12-06 13:34:06.052520 Mattie[1386:360881] Calling method with delay of: 120.0
2016-12-06 13:34:06.053162 Mattie[1386:360881] Scheduled study for 1 minutes in 120 seconds from now.
2016-12-06 13:34:06.066838 Mattie[1386:360881] Calling method with delay of: 180.0
2016-12-06 13:34:06.067027 Mattie[1386:360881] Scheduled finish in 180 seconds from now.

Pause is called:

2016-12-06 13:35:06.038307 Mattie[1386:360881] ON PAUSE
2016-12-06 13:35:06.065389 Mattie[1386:360881] Added pause timer for 1 minutes

Planned in at 13:34:06, called at 13:35:06

Planning notifications and methods calls using DispatchQueue.main.asyncAfter

2016-12-06 13:36:48.845838 Mattie[1390:361681] Calling method with delay of: 0.0
2016-12-06 13:36:48.847389 Mattie[1390:361681] Scheduled study for 1 minutes in 0 seconds from now.
2016-12-06 13:36:48.854336 Mattie[1390:361681] Calling method with delay of: 60.0
2016-12-06 13:36:48.854543 Mattie[1390:361681] Scheduled pause for 1 minutes in 60 seconds from now.
2016-12-06 13:36:48.861424 Mattie[1390:361681] Calling method with delay of: 120.0
2016-12-06 13:36:48.861601 Mattie[1390:361681] Scheduled study for 1 minutes in 120 seconds from now.
2016-12-06 13:36:48.868464 Mattie[1390:361681] Calling method with delay of: 180.0
2016-12-06 13:36:48.868644 Mattie[1390:361681] Scheduled finish in 180 seconds from now.

Pause is called:

2016-12-06 13:37:54.865400 Mattie[1390:361681] ON PAUSE
2016-12-06 13:37:54.897354 Mattie[1390:361681] Added pause timer for 1 minutes

Planned in at 13:36:48, called at 13:37:54

J_J
  • 123
  • 1
  • 7

1 Answers1

17

asyncAfter is only guaranteed to wait at least as long as specified.

You probably should still be using Timer if you want exact time intervals on iOS9.

Timer.scheduledTimer(timeInterval: delay, 
               target: self, 
             selector: #selector(executeClosure), 
             userInfo: nil, 
              repeats: false)

With executeClosure being a function that executes the last saved closure.

Tristan Burnside
  • 2,546
  • 1
  • 16
  • 23
  • Thanks, that makes sense. I think what is happening that code execution is delaying the main thread for six seconds. Between each study and pause session I am connecting to a VPN. I originally used Timer for iOS9 as well but the selector parameter caused some problems because the function takes one parameter. XCode wanted me to add @objc to my method. I'll see how to resolve that and stick to Timer for both iOS versions. – J_J Dec 06 '16 at 12:58
  • 6
    I have also experienced this problem (`asyncAfter` being not precise). In my case it was always late around 10% then expected. I was surprised that this is the only discussion I found on this subject, so I just wondered, how did you know that '`asyncAfter` is only guaranteed to wait at least as long as specified' @tristan-burnside ? :) I couldn't find any official docs on this matter. – tadija Apr 10 '17 at 17:31
  • I was into that problem too when trying to generalize some event defering functionality and wanted it to be done with gcd, but got myself using timer eventually as it makes sense. Hope nobody will be wasting time (~5h) as I did. Thanks Tristan – Dima Gimburg Apr 19 '17 at 15:24