130

How can I run a function every minute? In JavaScript I can do something like setInterval, does something similar exist in Swift?

Wanted output:

Hello World once a minute...

Arashsoft
  • 2,749
  • 5
  • 35
  • 58
JOSEFtw
  • 9,781
  • 9
  • 49
  • 67
  • Updated for Swift 2: [Swift Timer](http://www.ios-blog.co.uk/tutorials/swift/swift-nstimer-tutorial-lets-create-a-counter-application/) – JamesG Apr 05 '16 at 16:59

8 Answers8

192
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

func sayHello() 
{
    NSLog("hello World")
}

Remember to import Foundation.

Swift 4:

 var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)

 @objc func sayHello() 
 {
     NSLog("hello World")
 }
MujtabaFR
  • 5,956
  • 6
  • 40
  • 66
Unheilig
  • 16,196
  • 193
  • 68
  • 98
  • 1
    but what if you want to switch between view's? The code will stop right? – Cing Mar 20 '16 at 13:58
  • 7
    @Cing depends what is self referring to. – Antzi Mar 31 '16 at 14:44
  • 1
    Don't forget that `NSTimer` retains it's target, so, with this setup, if `helloWorldTimer` is a property on `self` you've got yourself a retain cycle, where `self` retains `helloWorldTimer` and `helloWorldTimer` retains `self`. – MANIAK_dobrii Sep 20 '16 at 08:58
  • @MANIAK_dobrii Can you also comment on how to break the retain cycle? If the VC is dismissed but the timer not cancelled, the cycle's still in tact? So you must both cancel the timer and then dismiss the view to break the cycle? – Dave G Dec 30 '16 at 11:17
  • @DaveG when `timer` holds `VC` and `VC` holds `timer` the cycle will hold forever until one of them releases the other, that's reference counting. The best way is to `-invalidate` the timer, this will release it's target and break the cycle. You `-invalidate` the timer when you've done with it, make sure you don't do that in a `VC`'s `-dealloc`, because dealloc is called after object got last release, and, you know, you had a retain cycle there you need to break. – MANIAK_dobrii Jan 11 '17 at 07:17
  • 1
    For Swift 4, the method of which you want to get the selector must be exposed to Objective-C, thus @objc attribute must be added to the method declaration. e.g ``` @objc func sayHello(){} ``` – Shamim Hossain Oct 18 '17 at 09:49
150

If targeting iOS version 10 and greater, you can use the block-based rendition of Timer, which simplifies the potential strong reference cycles, e.g.:

weak var timer: Timer?

func startTimer() {
    timer?.invalidate()   // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
    timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
        // do something here
    }
}

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

// if appropriate, make sure to stop your timer in `deinit`

deinit {
    stopTimer()
}

While Timer is generally best, for the sake of completeness, I should note that you can also use dispatch timer, which is useful for scheduling timers on background threads. With dispatch timers, since they're block-based, it avoids some of the strong reference cycle challenges with the old target/selector pattern of Timer, as long as you use weak references.

So:

var timer: DispatchSourceTimer?

func startTimer() {
    let queue = DispatchQueue(label: "com.domain.app.timer")  // you can also use `DispatchQueue.main`, if you want
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer!.schedule(deadline: .now(), repeating: .seconds(60))
    timer!.setEventHandler { [weak self] in
        // do whatever you want here
    }
    timer!.resume()
}

func stopTimer() {
    timer = nil
}

For more information, see the the Creating a Timer section of Dispatch Source Examples in the Dispatch Sources section of the Concurrency Programming Guide.


For Swift 2, see previous revision of this answer.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • how would you go for an execute-only-once timer within this approach? – Juan Boero Jan 21 '16 at 19:36
  • @JuanPabloBoero - I wouldn't use this approach for fire-once situation. You'd use `dispatch_after`. Or a non-repeating `NSTimer`. – Rob Jan 21 '16 at 20:25
  • @Rob I have a counter label on the screen which gets updated every second from a function, and I am using above code to increment my counter and update the label text. But, the issue I'm facing is that my counter variable gets updated every second, but the screen doesn't show the latest counter value in my UILabel. – Nagendra Rao Mar 11 '16 at 07:48
  • Rao, the above is useful for performing some action on a background thread. But your UI updates must happen on the main queue. So, either schedule this on the main thread or at least dispatch the UI updates back to the main thread. – Rob Mar 11 '16 at 08:11
  • Got it, I did that and now its updating the label text in main thread. There is another problem now, I want the process to run in background even when the app is minimised, but the process just stops when app is minimised(ie., when I open other app) and only resumes when I open the app again(ie., when I return to the app) How do I run this background thread independent of whether the app is minimised or not? Thank you for the quick response! – Nagendra Rao Mar 11 '16 at 08:29
  • 1
    This question does not belong here in the comments to this answer. Delete your comments above and post your own question. But, in short, you generally don't actually keep the app running in background, but only make it look like it was. See http://stackoverflow.com/a/31642036/1271826. – Rob Mar 11 '16 at 09:19
  • (1) DispatchSource lets you pick wall time, or CPU time. Sometimes important. (2) DispatchSource/NSTimer for one shots is more strightforward to cancel. Canceling a dispatch after involves creating a mutable value for "please don't act", checking it, and can extend object lifetimes. That may or may not be important depending on what one wants. – Stripes Apr 23 '18 at 17:28
  • @Stripes - Yep, I largely agree, though canceling a dispatched block no longer involves creating your own mutable state variable and/or extending lifetimes: GCD now supports cancelation; you can [`cancel`](https://developer.apple.com/documentation/dispatch/dispatchworkitem/1780910-cancel) a `DispatchWorkItem` and even periodically check [`isCancelled`](https://developer.apple.com/documentation/dispatch/dispatchworkitem/1780829-iscancelled) if the dispatched block is already running (saving you from having to create your own thread-safe state variable/property). – Rob Apr 23 '18 at 17:34
  • Ack! I keep forgetting things they added to dispatch after I left that team! – Stripes Apr 23 '18 at 17:40
22

If you can allow for some time drift here's a simple solution executing some code every minute:

private func executeRepeatedly() {
    // put your code here

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
        self?.executeRepeatedly()
    }
}

Just run executeRepeatedly() once and it'll be executed every minute. The execution stops when the owning object (self) is released. You also can use a flag to indicate that the execution must stop.

algrid
  • 5,600
  • 3
  • 34
  • 37
  • 1
    This decision is way more convenient than having around all these timers, adding them to runloops, invalidating them… Cool! – julia_v Apr 23 '19 at 11:39
  • this seems valid solution but it is creating a retain cycle when I am using it with my web service call... – jayant rawat Feb 03 '20 at 06:58
20

Here's an update to the NSTimer answer, for Swift 3 (in which NSTimer was renamed to Timer) using a closure rather than a named function:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
John T
  • 774
  • 8
  • 10
12

You can use Timer (swift 3)

var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)

In selector() you put in your function name

Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
Bas
  • 4,423
  • 8
  • 36
  • 53
7

In swift 3.0 the GCD got refactored:

let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)

timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
    NSLog("Hello World")
}
timer.resume()

This is specially useful for when you need to dispatch on a particular Queue. Also, if you're planning on using this for user interface updating, I suggest looking into CADisplayLink as it's synchronized with the GPU refresh rate.

Can
  • 8,502
  • 48
  • 57
0

Here is another version algrid's answer with an easy way to stop it

@objc func executeRepeatedly() {

    print("--Do something on repeat--")
        
    perform(#selector(executeRepeatedly), with: nil, afterDelay: 60.0)
}

Here's an example of how to start it and stop it:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    executeRepeatedly() // start it
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    NSObject.cancelPreviousPerformRequests(withTarget: self) // stop it
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
0
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true, block: myMethod)

func myMethod(_:Timer) {
...
}

or

timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
...
}

make sure to invalid the timer at some point like your time is no longer visible, or you object is deist

Nathan Day
  • 5,981
  • 2
  • 24
  • 40