Search code examples
iosswiftnstimerperformselector

iOS: How to call a function sequentially after different (defined) amount of time?


I have an array that contains time values in milliseconds e.g.:

let executionTimes = [0, 1500, 3500, 4700]

On top there is a function that simply does anything - for now - print some text:

func printHello() {
    print("Hello")
}

What I'd like to do is execute the function after the given periods of time in the array. So printHello() will be executed immediately, then after 1,5 secs, then after another 2 secs and finally after another 1,2 secs before beginning from the start again. For now I want this timing of function calling to continue "forever".

First I tried using NSTimer to solve this, but from what I found out it is not possible to dynamically change the timeInterval of a timer without invalidating it first and this tempers with the given time schedule.

Then I thought using performSelector could solve this:

func executeCustomScheduler(timeIndex: Int) {
    let time: Double = executionTimes[timeIndex]

    NSLog("Hello")

    let nextTimeIndex = timeIndex == (executionTimes.count - 1) ? 0 : timeIndex + 1

    self.performSelector(Selector(executeCustomScheduler(nextTimeIndex)), withObject: self, afterDelay: time)
}

This doesn't work at all as the function is being executed without delay at all, and runs into a crash because of the recursive calls I think.

Could anybody please provide any useful hint on this?

Thanks a lot!


Solution

  • There are multiple things wrong here:

    1. Selector(executeCustomScheduler(nextTimeIndex)) creates a selector based on the return value of executing executeCustomScheduler(nextTimeIndex) one time
    2. withObject: self - why would you pass self?
    3. why are you using performSelector at all?
    4. executionTimes is not in milliseconds but in seconds, or at least the method expects them to be
    5. final problem: your argument is a primitive Int which gets really troublesome when passing it around in the performSelector

    Solutions:

    1. Selector("executeCustomScheduler:") is the correct selector
    2. nextTimeIndex is the correct argument to pass along
    3. it would probably be better to use something that has a bit more type security, anything with selectors is wide open to a ton of problems regarding renaming e.g.
    4. divide the values by 1000 before passing them into the performSelector
    5. I do not really have a good solution: the easiest and probably worst one: use a String instead of an Int :/

    Better solution:
    Use dispatch_after, get rid of those pesky selectors and be typesafe all the way through:

    class Obj {
    
        let executionTimes = [0, 1500, 3500, 4700]
    
        func executeCustomScheduler(timeIndex: Int) {
            NSLog("Hello")
    
            let time = executionTimes[timeIndex]
            let nextTimeIndex = (timeIndex + 1) % executionTimes.count
    
            let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(time) / 1000 * Double(NSEC_PER_SEC)))
            dispatch_after(delayTime, dispatch_get_main_queue()) {
                self.executeCustomScheduler(nextTimeIndex)
            }
        }
    }
    
    let obj = Obj()
    obj.executeCustomScheduler(0)