1
votes

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!

3
dispatch_async_after is your friend. Learn GCD and embrace it.gnasher729

3 Answers

0
votes

You can use NSTimer for this.

NSDate *date = [[NSDate date] dateByAddingTimeInterval:someInterval];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:date interval:0 target:self selector:@selector(timerFired:) userInfo:info repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

This way, you can add your intervals to the date, and schedule timers (yes, many of them, for each interval) to fire at that time. Hope this helps. Also, you should keep timers in some array if you want to invalidate timers any time later. Good luck!

PS. Sorry for the objective c thing, hope you can convert it to the swift.

0
votes

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)
0
votes

You don't have to use us NSTimer in order to achieve what you want. In fact, the solution to your problem is pretty simple.

Example solution in Swift:

let executionTimes = [1000.0, 1500.0, 3500.0, 4700.0]

for executionTime in executionTimes {
    self.performSelector(#selector(printHello(_:)), withObject: NSNumber(double: executionTime), afterDelay: executionTime/1000.0)
}

func printHello(executionTime: NSNumber) {
     let doubleValue = executionTime.doubleValue
     print("Hello \(doubleValue)")
}

Note that you can't pass struct as an argument labelled with withObject: label. That is why I wrap Double (which is a struct) instance with NSNumber (which is a class).

Example solution in Objective-c:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    NSArray<NSNumber *> *executionTimes = @[@(1000.0), @(1500.0), @(3500.0), @(4700.0)];
    for (NSNumber *executionTime in executionTimes) {
        [self performSelector:@selector(printHello:) withObject:executionTime afterDelay:executionTime.doubleValue/1000];
    }

    return YES;
}

- (void)printHello:(NSNumber *)executionTime
{
    NSLog(@"Hello %@", executionTime.stringValue);
}