Cancel Swift Timer From Another Function - swift

I am using a Swift Timer library found here. It allows you to run a timer and then stop it using the following syntax:
Timer.every(5.seconds) { (timer: Timer) in
// do something
if finished {
timer.invalidate()
}
}
I am trying to have the timer start and then have the option of the timer being cancelled from another function but I can't figure out how to reference the timer that is counting down from another function. I have tried doing something like this but it throws the following error:
Static member every cannot be used on instance of type Timer
var countTimer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
initNotificationSetupCheck()
countTimer.every(5.seconds) { //error here
}
func stopTimer() {
countTimer.invalidate()
}

What if you were to use a mixture of both, but instead of creating an instance of Timer, assign the timer from inside the closure to your member variable like:
var countTimer: Timer
Timer.each(5.seconds) { timer in
countTimer = timer
//rest of code
}
Then you can invalidate the timer outside of the closure if needed.

You can go like this
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self,
selector: #selector(self.eventForTimer), userInfo: nil, repeats: true)
for cancelling timer
func cancelTimer() {
self.timer.invalidate()
}

Related

Scheduled Timer won't fire

I am trying out Swift as a language for CLI tool, which is supposed to serve as a simple web crawler.
In my main file I create an instance of APIFetcher class. In the initialiser of APIFetcher I instantiate an instance of Timer with the given time interval. Once I call startQuerying method, it adds Timer to the main run loop - at this point I would expect performTask method would be invoked, but it isn't. What am I doing wrong?
#available(OSX 10.12, *)
public init(with interval: TimeInterval) {
self.timer = Timer(timeInterval: interval, repeats: true) { _ in
self.performTask()
}
}
deinit {
self.timer?.invalidate()
self.timer = nil
}
public func startQuerying(_ url: URL) {
guard let unwrappedTimer = self.timer else { return }
RunLoop.main.add(unwrappedTimer, forMode: .defaultRunLoopMode)
}
func performTask() {
print("Performed scheduled task")
}
Thanks vadian you are right, I added the timer to run loop, but never actually started it. This fixes the whole issue:
RunLoop.main.add(unwrappedTimer, forMode: .defaultRunLoopMode)
RunLoop.main.run()
Also, see When Would You Use a Run Loop? documentation

Swift NSTimer Pause Resume

I am programming with Swift, and I have a problem.
I am writing code using a timer using NSTimer, and I have succeeded in stopping the timer. I created a button to restart the timer, but I do not know how to enter the code.
import UIKit
class ViewController: UIViewController {
/*Timer1*/
var landmarkTime = 0
var landmarkNSTimer = Timer()
/*Timer3*/
func landmarkTimer() {
landmarkTime += 1
}
/*PauseButton*/
#IBAction func PauseButton(_ sender: Any) {
landmarkNSTimer.invalidate()
}
/*ResumeButton*/
#IBAction func ResumeButton(_ sender: Any) {
/*I want to restart the timer in this part, but I do not know what code to put in.*/
}
override func viewDidLoad() {
super.viewDidLoad()
/*Timer2*/
landmarkNSTimer = Timer(timeInterval: 1, target: self, selector: #selector(ViewController.landmarkTimer), userInfo: nil, repeats: true)
RunLoop.main.add(landmarkNSTimer, forMode: RunLoopMode.commonModes)
}
}
/* ResumeButton */ This is a problem. I typed /* Timer2 */ in this part, but I have an unknown problem and am looking for another code. Tell the new code to be entered in /* ResumeButton */ so that the timer can be restarted.
You didn't pause the timer, you stopped it. If you read the documentation for invalidate, it says [emphasis added]:
Stops the receiver from ever firing again and requests its removal from its run loop.
Timers don't pause; it's simply not part of their API. If you want a pause/resume behavior, there are a variety of ways to get it, including:
Create a new timer when resuming.
Set the fireDate to Date.distantFuture when pausing, then set it to some time in the near future when resuming.
Use a boolean variable to indicate the paused state, and have your timer callback check that variable when the timer fires. If paused == true, do nothing.

Timer isn’t performed in the case of sub-task

I’m trying to implement a function using timer and have found timer is not performed in case that it is called through callback function of “URLSession.dataTask”.
In below case, “callee” function is called.
class TimerClass {
func caller() {
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(callee),
userInfo: nil,
repeats: false)
}
func callee() {
print(“OK”)
}
}
class AnyClass {
func any() {
let timer:TimerClass=TimerClass()
timer.caller()
}
}
But below “callee” is not called. (I’ve confirmed “caller” function is performed)
class TimerClass {
func caller() {
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(callee),
userInfo: nil,
repeats: false)
}
func callee() {
print(“OK”)
}
}
class AnyClass {
func any() {
func cb(data:Data?, response:URLResponse?, err:Error?) {
let timer:TimerClass=TimerClass()
timer.caller()
}
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: cb)
.
.
.
}
}
I think maybe because it was performed by sub-task.
Can anyone let me know how do I correct the code?
Check the reference of the Timer class:
Use the scheduledTimer(timeInterval:invocation:repeats:) or scheduledTimer(timeInterval:target:selector:userInfo:repeats:) class
method to create the timer and schedule it on the current run loop in
the default mode.
Use the init(timeInterval:invocation:repeats:) or init(timeInterval:target:selector:userInfo:repeats:) class method to
create the timer object without scheduling it on a run loop. (After
creating it, you must add the timer to a run loop manually by calling
the add(_:forMode:) method of the corresponding RunLoop object.)
So, if you want to schedule the timer in the main RunLoop, you can write something like this:
DispatchQueue.main.async {
Timer.scheduledTimer(
timeInterval: 0.1,
target: self,
selector: #selector(self.callee),
userInfo: nil,
repeats: false
)
}
Not using the Timer class, but this seems to be better:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.callee()
}
(UPDATED) It is clearly stated that the RunLoop class is generally not considered to be thread-safe. You should not use the old code (hidden in the edit history), even if it seemingly works in some limited conditions.

Swift NSTimer didn't work

I make a "Command Line Tool", and I need use NSTimer.
I start the timer, but it doesn't work...
import Foundation
class ct : NSObject {
func start() {
var timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
println("test timer");
}
}
var a = ct();
a.start()
while(true) { sleep(10000000) }
NSTimer needs a run loop to work properly, a CLI doesn't have/need one by default.
Call
CFRunLoopRun()
to start the run loop and
CFRunLoopStop(CFRunLoopGetCurrent())
to stop it and don't forget to return appropriate return values.

NSTimer.scheduledTimerWithTimeInterval in Swift Playground

All the examples I've seen on using the "NSTimer.scheduledTimerWithTimeInterval" within Swift show using the "target: self" parameter, but unfortunately this doesn't work in Swift Playgrounds directly.
Playground execution failed: <EXPR>:42:13: error: use of unresolved
identifier 'self'
target: self,
Here's an example referenced above that results in the error:
func printFrom1To1000() {
for counter in 0...1000 {
var a = counter
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: self,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
You really should not be using NSTimer these days. It's consumes a lot of resources, causes unnecessary battery drain, and the API lends itself to ugly code.
Use dispatch_after() instead:
dispatch_after(0, dispatch_get_main_queue()) { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
Of course, since the timer will fire after playground does it's stuff you will need an equivalent of timer.fire() to force the code to execute immediately instead of after a 0 second delay. Here's how that works:
let printFrom1To1000 = { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
dispatch_after(0, dispatch_get_main_queue(), printFrom1To1000)
printFrom1To1000()
To get this to run directly within a Swift Playground, you need to embed the printFrom1To1000 function within a class and then set an instance of that class to the "target:" parameter instead of using "self".
Here's a full working example:
class myClass: NSTimer{
func printFrom1To1000() {
for counter in 0...1000 {
var b = counter
}
}
}
let myClassInstance = myClass()
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: myClassInstance,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
If you already have an object you are referencing (i.e., updating a label), you can extend that type and use that function as the Selector. I find this easier than creating a whole new class and instantiating a new object from it.
extension SKLabelNode {
func updateMe() {
count++
label.text = "\(count)"
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0.25,
target: label,
selector: Selector("updateMe"),
userInfo: nil,
repeats: true)
timer.fire()