29

I have this problem for a few days now and I don't get what I am doing wrong.

My application is basically just creating some timers. I need to stop them and create new ones. But at the moment stopping them doesn't work.

self.timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target:self, selector: "timerDidEnd:", userInfo: "Notification fired", repeats: false)

That's my timer

func timerDidEnd(timer:NSTimer){
    createUnrepeatedAlarmWithUpdateInterval()
}

Because my timer didn't want to stop I am currently using the unrepeated timer and start it myself after it stopped.

func stopAlarm() {

    if self.timer != nil {
        self.timer!.invalidate()
    }
    self.timer = nil
    self.timer = NSTimer()
}

And that's how I stop my timer.

alarmManager.stopAlarm()
alarmManager.createUnrepeatedAlarmWithUpdateInterval()

I call the stopAlarm() function before creating a new timer.

I really don't know what I am doing wrong so I appreciate every answer :)

class AlarmManager: ViewController{

private var timer : NSTimer?
private var unrepeatedTimer : NSTimer?
private let notificationManager = NotificationManager()
private var current = NSThread()
private let settingsViewController = SettingsViewController()

func createRepeatedAlarmWithUpdateInterval(){

    var timeInterval:NSTimeInterval = settingsViewController.getUpdateIntervalSettings()

    if timer == nil{
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
        target: self,
        selector: "repeatedTimerDidEnd:",
        userInfo: "Notification fired",
        repeats: true)
    }
}
func repeatedTimerDidEnd(repeatedTimer:NSTimer){
    ConnectionManager.sharedInstance.loadTrainings(settingsViewController.getServerSettings())
    createUnrepeatedAlarm(10)
}

func createUnrepeatedAlarm(timeInterval:Double){

    unrepeatedTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
        target: self,
        selector: "unrepeatedTimerDidEnd:",
        userInfo: "Notification fired",
        repeats: false)
}
func unrepeatedTimerDidEnd(unrepeatedTimer:NSTimer){
    notificationManager.createNotification(self, reminderType: NotificationManager.ITEMRATINGREMINDER)
    notificationManager.createNotification(self, reminderType: NotificationManager.ITEMREMINDER)
    print("UnrepeatedAlarm ended")
}

func stopAlarm(){
    print("StopAlarm triggered")
    if (timer != nil)
    {
        print("stoptimer executed")
        timer!.invalidate()
        timer = nil
    }

    if (unrepeatedTimer != nil)
    {
        unrepeatedTimer!.invalidate()
        unrepeatedTimer = nil
    }
}
}

Thats the whole code of this class. Maybe that helps :D

Banelu
  • 293
  • 1
  • 3
  • 9
  • 3
    Are you sure the timer doesn't stop? Could it be you are creating multiple timers? – Cristik Dec 29 '15 at 09:07
  • Make sure you call `stopAlarm()` from the main thread. – Marius Fanu Dec 29 '15 at 09:17
  • @MariusFanu I definitely call stopAlarm from the main thread. I printed out the thread to be sure and it was the same one. – Banelu Dec 29 '15 at 09:19
  • @Cristik If timerDidEnd is executed I print out a line so I know it did end. If I use stopAlarm() on the same thread after that the line will be printed out anyway. So I don't think the timer stops. I also dont think its possible to create multiple timers in this case because I always use the same variable so it would be overwritten, right? :D – Banelu Dec 29 '15 at 09:23
  • The variable would be overwrited, but the previous timer would still be left running if it's not invalidated before overwriting the variable. With the current code it doesn't seem to be the case, but you say the problems are with the original code, with btw you should show to us – Cristik Dec 29 '15 at 09:35
  • @Cristik I stop the timer before creating the new one so the old one should be stopped and not be active anymore. The original code only has some more funktion calls but nothing that would be interesting for you I think. I can add it to my question but Its a bit more code :D – Banelu Dec 29 '15 at 09:42
  • What value are you using for timeInterval? – giorashc Dec 29 '15 at 09:54
  • @giorashc At the moment I am using 5 seconds. But just for testing. If everything works the user can choose between 15 minutes, 30 minutes and 60 minutes. – Banelu Dec 29 '15 at 09:56
  • 5 seconds as 5.0 passed to the function right? (just to make sure you are not passing it in ms) – giorashc Dec 29 '15 at 10:08
  • No its 5. The funktion works with seconds. It says: "seconds The number of seconds between firings of the timer." That part of the timer does work perfect. It is getting fired every 5 seconds. But thats not my problem :D – Banelu Dec 29 '15 at 10:10
  • why *my timer didn't want to stop* ? And what's the purpose to set the timer to nil and in the next line to a generic instance? – vadian Dec 29 '15 at 10:25
  • @vadian I just do this as It was mentioned in an answer to this question on an other side. Its not wrong, isn't it? – Banelu Dec 29 '15 at 10:32
  • 1
    It's not wrong but meaningless. If the timer is not running it's supposed to be `nil`. A generic instance `NSTimer()` is not `nil` but does actually nothing. – vadian Dec 29 '15 at 10:37
  • @vadian okay, thats what I thought. Thanks for that advise! – Banelu Dec 29 '15 at 10:40

5 Answers5

50

The usual way to start and stop a timer safely is

var timer : Timer?

func startTimer()
{
  if timer == nil {
    timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
  }
}

func stopTimer()
{
    timer?.invalidate()
    timer = nil
}

startTimer() starts the timer only if it's nil and stopTimer() stops it only if it's not nil.

You have only to take care of stopping the timer before creating/starting a new one.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thats basically what I am doing. I think my problem is somewhere else. I printed a line when stopTimer() is executed and I printed a line inside the if. I never saw the line inside the if in my output so I never get into the if. So the problem is my if. Help? :D – Banelu Dec 29 '15 at 11:02
  • Your code looks a bit confusing, the "repeated" timer is actually unrepeated too. Try a repeating timer using my code. – vadian Dec 29 '15 at 11:16
  • I dont have a repeated timer. They are both unrepeated. I wrote that a few lines above in my question. The problem isn't the creation of the timer or that stopTimer() isn't used or whatever. It is that the "if timer != nil" is never true. Why is that? – Banelu Dec 29 '15 at 11:24
  • Okay I changed it to repeated Alarm but its still the same problem. Of course it is. Because the problem is still that its not using the if. I update the code in my question so you can tell me what I am doing wrong with the if :D – Banelu Dec 29 '15 at 11:45
  • If the timer **is** `nil` you don't have to invalidate it. I recommend to set breakpoint and look at the state of the variables – vadian Dec 29 '15 at 11:49
  • Okay, so my problem is that timer always is nil. But why? Thats not how it should be. I used timer 1 sec ago to create the timer and after that its nil without me doing something?! – Banelu Dec 29 '15 at 11:52
  • I did and that was my answer :D Both timers are always nil. – Banelu Dec 29 '15 at 11:58
  • So thank you very much for every answer. The problem was at a completely different part of my code. I was creating multiple instances of AlarmManager. I guess my excuse is that I am using swift for only 2-3 Weeks now :D Anyway thanks a lot! – Banelu Dec 29 '15 at 12:16
  • `nil` cannot be assigned to type `Timer` – Chase Roberts Jun 26 '17 at 19:17
  • @ChaseRoberts I added how to use the code in Swift 3 – vadian Jun 26 '17 at 19:23
  • can't you just write your stopTimer function as : `{ timer?.invalidate(); timer = nil`} and no longer have the `if timer != nil` check? Or is that you're doing this to improve harmony with the `if timer == nil` on the `startTimer`? To me the major key is in the `if timer == nil`... – mfaani Aug 10 '17 at 11:28
  • @Honey Yes you can, I just wanted to show the balanced start/stop functionality *check-if-running* and *check-if-not-running* – vadian Aug 10 '17 at 12:03
  • Thanks, this solved my problems with calling `.invalidate()`, way better the approach you took. Setting it to `nil` is the solution – Ivan Cantarino Apr 12 '18 at 11:04
  • This answer definitely helps. It makes a lot of sense too – Lance Samaria Nov 26 '21 at 02:04
  • you really don't need to force unwrap. Just call timer?.invalidate(), timer = nil. – Adam Levy Sep 19 '22 at 21:54
17

Make sure you're calling invalidate on the same thread as the timer.

From the documentation:

Special Considerations You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.

https://developer.apple.com/documentation/foundation/nstimer/1415405-invalidate?language=objc

Kevin Kruusi
  • 231
  • 3
  • 5
6

Something that's not really covered by the previous answers is that you should be careful your timer isn't scheduled multiple times.

If you schedule a timer multiple times without first invalidating it, it'll end up scheduled on multiple run loops, and invalidating it then becomes nigh impossible.

For me, it happened when calling my scheduleTimer() function in separate functions in my view controller's life cycle (viewWillAppear, viewDidAppear, ...)

So in short, if you aren't sure (or you cannot guarantee) your Timer is only scheduled once, just always invalidate it first.

Skwiggs
  • 1,348
  • 2
  • 17
  • 42
  • 1
    This was my problem! I didn't realize I had scheduled the time in a couple of different places. I now have one caller and it stops like it is supposed to. Thank you! – SouthernYankee65 Feb 12 '21 at 21:31
0

I have tried every possible solution found but not able to resolve that at the end I have set repeat "false" while initialising timer like below

self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.methodname), userInfo: nil, repeats: false)

And need to add above line in my selector method for whatever the condition for which I wanted to repeat the time.

For example:- My requirement is I want to repeatedly call some method until one condition satisfied. So instead of adding repeats true I set it false as repeat true does not invalidate timer in my case.

I have added below in my viewdidload method

self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)

in selector function I added below code

@objc func method{

if condition not matched{
   self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)
 }
 else{
    // once you are here your timer invalidate automatically
 }

}

Hope this will solve your problem

Priyanka
  • 159
  • 2
0

For Swift 5 Xcode 12.4 there is example to use timer:

class MyController: UIViewController {

    private id: Float;

    func setValue(_ value: Float, withAnimation: Bool) {
            
            let step: Float = value / 200
            var current: Float  = withAnimation ? 0.0 : value
            
            let _ = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: withAnimation) { timer in
                
                DispatchQueue.main.async {
                    
                    self.id = current
                    
                    current += step
                    
                    if current > value || withAnimation == false {
                        self.id = current
                        
                        timer.invalidate()
                    }
                }
            }
        }
}
  • Can you please explain what this is doing? I'm still kind of new with Swift and I am writing for macOS. I'm having this issue as well and I think I understand some of what is going on in your answer. I just don't understand exactly how this is working. – SouthernYankee65 Feb 11 '21 at 13:38
  • Strange, because the question marked as iOS. In example we have create timer with Timer. scheduledTimer class method and as the last parameter of this call we provide a closure (function) that will call every time if repeats is true, or just one time if not. Inside this closure we check if current value is > value or withAnimation is false and invalidate timer if so, invalidation is stop and release timer. Hope it explain. – Sergei Sevriugin Feb 12 '21 at 06:14
  • I think I understand, but there's no use example of calling this method from the body of the class. Right now my method is ```func startTimer() {if Thread.isMainThread { timer?.invalidate() timer = nil timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateViewsWithTimer), userInfo: nil, repeats: true)}``` and my code just calls startTimer() when I need it to fire. When I need it to stop I try to use ```timer.invalidate(); timer = nil``` – SouthernYankee65 Feb 12 '21 at 18:39
  • I'm also getting an error that ViewController does not have a member "item". I know the OP was using this for iOS, but I would like to adapt this to macOS. – SouthernYankee65 Feb 12 '21 at 18:41
  • It was just example how to use, you need to adopt it to you case. Main idea is create timer with closure and use condition inside the closure to dismiss the timer. The question was marked as iOS so I have answered for this environment – Sergei Sevriugin Feb 12 '21 at 19:08