Search code examples
swiftnstimer

Swift Selector is never called for NSTimer


I have the following instance method in my Swift class Sentence which makes a call to a NSTimer which calls the class instance method as its Selector. When I run the program without breakpoints, it gets to the first NSTimer successfully but then stalls at NSTimer. When I add a breakpoint to see if sentenceDidFinish is ever called, I see that it never is, proving it stops at the first NSTimer.

class Sentence : NSObject {
   //init() etc.

   func playEvent(eventIndex : Int){
       if (eventIndex < 2){
           let currEvent = self.eventArray[eventIndex]
           currEvent.startEvent()
           let nextIndex = eventIndex + 1
           print("Play Event event id is ", eventIndex)
           NSTimer.scheduledTimerWithTimeInterval(currEvent.duration, target: self, selector: Selector("playEvent:"), userInfo: NSNumber(integer: nextIndex), repeats: false)
       }
       else if (eventIndex==2){
         self.eventArray[eventIndex].startEvent()
         print("Play Event event id is ", eventIndex)
         NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("sentenceDidFinish"), userInfo: nil, repeats: false)
      }
      else{
        //DO Nothing
      }
  }

  func sentenceDidFinish(){
    //foo
    //bar
  }

}

Here is the full .swift file:

https://gist.github.com/anonymous/e0839eae1d77e1e4b671


Solution

  • When you call playEvent: with the timer, the argument passed will be the timer itself, not the integer. But in the declaration for eventIndex you are acting as if it will be the integer.

    Try adding a method like this:

    func handleTimer(timer: NSTimer) {
        playEvent(timer.userInfo as! Int)
    }
    

    Then call the first timer like this:

    NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "handleTimer:", userInfo: NSNumber(integer: nextIndex), repeats: false)
    

    The forced casting (as!) will crash if userInfo isn't castable to Int. Safer, but more verbose would look like:

    func handleTimer(timer: NSTimer) {
        guard let index = timer.userInfo as? Int else { return }
        playEvent(index)
    }