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...
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...
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")
}
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
. –
Palatinate 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. –
Palatinate 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.
dispatch_after
. Or a non-repeating NSTimer
. –
Cognac cancel
a DispatchWorkItem
and even periodically check isCancelled
if the dispatched block is already running (saving you from having to create your own thread-safe state variable/property). –
Cognac 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.
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")
}
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
Timer
... NSTimer
has been renamed –
Botany 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.
You can perform a for-loop as simple as:
for try await tick in Every(.seconds(3)) {
print(tick)
}
By implementing this simple async sequence:
struct Every: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Int
var duration: Duration
private(set) var tick = 0
init(_ duration: Duration) { self.duration = duration }
func makeAsyncIterator() -> Self { self }
mutating func next() async throws -> Element? {
try await Task.sleep(for: duration)
guard !Task.isCancelled else { return nil }
tick += 1
return tick
}
}
Of course you can perform it asynchronously by wrapping the whole thing in a Task
and cancel the task anytime you want like this:
let clock = Task {
for try await tick in Every(.seconds(3)) {
print(tick)
if tick == 3 { clock.cancel() } // 👈 Cancel the task after 3 ticks
}
}
Performing this in the .task
modifier will automatically bind the lifetime of the iteration to the lifetime of the View
. No need to worry about invalidating at all:
.task {
do {
for try await tick in Every(.seconds(1)) { print(tick) }
} catch {
print(error)
}
}
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
}
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
© 2022 - 2024 — McMap. All rights reserved.