Swift Selector is never called for NSTimer - swift

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

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)
}

Related

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 Swift difficulty

I am trying to create a NSTimer so that I can move a UIImageView down but
The NSTImer is having difficulty, saying first that this.
var timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target:self(), selector: Selector ("mrockdown"), userInfo: nil, repeats: true)
is missing argument for parameter #1 in call. But when I remove the brackets from the target:self() it tells me
Cannot invoke 'scheduledTimerWIthTimerInterval' with an argument list of type '(Double, target: ViewController -> () -> ViewController, selector: Selector, userinfo: nil, repeates Bool.
What should I do?
The problem has to do with where you are saying this. It looks like you are trying to say this as part of a property declaration:
class ViewController {
var timer = ...
// ...
}
But you can't do that, because there is no self as far as a stored property is concerned. You need to declare the timer as an Optional and then initialize it later:
class ViewController {
var timer = NSTimer!
func someMethod {
timer = ...
}
}
Then you will remove the parentheses (they are wrong) and everything will compile just fine.
You can do this inside the function where you want to trigger the timer (i.e. viewDidLoad or some IBAction)
_ = Timer.scheduledTimer(timeInterval: yourInterval, target: self, selector: #selector(yourFunction), userInfo: nil, repeats: true)
That being said, to animate a view you should use this instead:
UIView.animate(withDuration: yourDuration) {
// set yourView's final position here
}

swift NSTimer userinfo

I'm trying to pass a UIButton with a NSTimer's userinfo. I've read every post on stackoverflow on NSTimers. I'm getting very close but can't quite get there. This post has helped
Swift NSTimer retrieving userInfo as CGPoint
func timeToRun(ButonToEnable:UIButton) {
var tempButton = ButonToEnable
timer = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("setRotateToFalse"), userInfo: ["theButton" :tempButton], repeats: false)
}
the function the timer runs
func setRotateToFalse() {
println( timer.userInfo )// just see whats happening
rotate = false
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
timer.invalidate()
}
I realise you've managed to fix this but I thought I would give you a little more information about using NSTimer. The correct way to access the timer object and hence user info is to use it like below. When initialising the timer you can create it like this:
Swift 2.x
NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("setRotateToFalse:"), userInfo: ["theButton" :tempButton], repeats: false)
Swift 3.x<
Timer.scheduledTimer(timeInterval: 1, target: self, selector:#selector(ViewController.setRotateToFalse), userInfo: ["theButton" :tempButton], repeats: false)
Then the callback looks like this:
func setRotateToFalse(timer:NSTimer) {
rotate = false
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
timer.invalidate()
}
Therefore you don't need to keep a reference to the timer and avoid often nasty global variables where possible. You may run into an issue in swift if your class doesn't inherit from NSObject where it says there is no callback defined but this can be easily fixed by adding #objc at the beginning of the function definition.
macOS 10.12+ and iOS 10.0+ introduces a block based API of Timer which is a more convenient way
func timeToRun(buttonToEnable: UIButton) {
timer = Timer.scheduledTimer(withTimeInterval:4, repeats: false) { timer in
buttonToEnable.enabled = true
}
}
A one shot timer will be invalidated automatically after it fires.
An similar convenient way for a one shot timer is using GCD (DispatchQueue.main.asyncAfter)
func timeToRun(buttonToEnable: UIButton) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) {
buttonToEnable.enabled = true
}
}
I was just going to post this as I read over it before I posted. I noticed that I had timer.invalidate() before userinfo so that's why it wasn't working. I will post it as it may help somebody else.
func setRotateToFalse(timer:NSTimer) {
rotate = false
timer.invalidate()
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
}

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()