looped timer does not invalidate as instructed - swift

I have built a "metal detector" kind of app that runs the "calculateDistance" function every 2 seconds, which calculates the distance in meters between the user location and a set marker, sets that to var globalDistance. Depending on if that distance, for simplicity, is >=10meters or <10meters, I am playing a scheduled timer that calls the "audioplayer" function, which plays a "beep" sound every 2 seconds (if distance>=10m) or every 0.5seconds (if distance <10m).
Problem is, the timers never invalidate as I instruct them to. So if I move from <10m to >10m with my device, the 0.5sec beeping continues. I do audioTimer.invalidate() to stop the timer running from previous iteration.
Any idea what I am doing wrong with my code? Many thanks
func calculateDistance {
//here there is code that successfully calculates distance, every 2 seconds
var timerSeconds = 0.0
var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
timerSeconds = 2
}
if globalDistance >= 0 && globalDistance <= 10 {
timerSeconds = 0.5
}
audioTimer.invalidate()
audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
audioTimer.fire()
}
func audioPlayer(){
AudioServicesPlaySystemSound(1104)
}

The basic idea is to make sure there is no code path by which a Timer is started without stopping any prior one. You current code has a couple of paths by which an existing timer is not invalidated before starting the next one.
Furthermore, I would suggest that you only invalidate the old Timer if the new beep frequency is different than the old beep frequency. (Why invalidate the 2 second repeating beeping and start another timer if the old 2 second timer will do the job fine?)
So, this means that you will:
pull both the Timer and the TimeInterval variables out of the function;
only do the "new timer" process if it decides that the beep interval has changed; and
make sure to always invalidate the old timer before creating a new one.
For example:
private var audioTimer: Timer?
private var beepInterval: TimeInterval?
private func updateBeepIntervalIfNeeded() {
// here there is code that successfully calculates distance, called with whatever frequency you want
let newBeepInterval: TimeInterval
if globalDistance > 10 {
newBeepInterval = 2
} else if globalDistance >= 0 {
newBeepInterval = 0.5
} else {
fatalError("less than 0?!") // I'm inferring from your code that this cannot happen, but by using `let` above, Swift warned me that we had a path of execution we hadn't previously considered
}
if beepInterval != newBeepInterval {
beepInterval = newBeepInterval
audioTimer?.invalidate()
audioTimer = Timer.scheduledTimer(timeInterval: beepInterval!, target: self, selector: #selector(beep(_:)), userInfo: nil, repeats: true)
audioTimer!.fire()
}
}
#objc func beep(_ timer: Timer) {
// perform beep here
}

The problem
There's several issues at hand here.
Firstly, I'd like to emphasis the difference between references and instances. When you call call an initializer, the system allocates a piece of memory for a new object, and gives you a reference to that memory, which is stored in whatever variable you assign it to. You can assign this reference to other variables, which will make copies of the reference. Each of these variable references the same original object. This object will continue to exist in memory until no more variables reference it.
In your case, you're not directly calling an initializer, but you're calling a static method which serves a similar purpose. A new object is allocated on your behalf, and you're given a reference, which you then assign to audioTimer. There's a catch to this, however. When you call Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:), the newly constructed timer is scheduled on the current run loop for you. The run loop is what's in charge of firing your timer at the right time. The consequence of this is that now the runloop is referencing your timer, preventing the timer object from being destroyed. Unless you invalidate your timer to unregister it from its runloop, the timer will continue to exist and fire forever, even after you delete your deference to it.
Now let's take a look at your code, with some explanation as to what's going on:
func calculateDistance {
//here there is code that successfully calculates distance, every 2 seconds
var timerSeconds = 0.0
// 1) Initialize timer #1
var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
timerSeconds = 2
}
if globalDistance >= 0 && globalDistance <= 10 {
timerSeconds = 0.5
}
// 2) Invalidate timer #1 (timer #1 is useless)
audioTimer.invalidate()
// 3) Initialize timer #1
audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
// 4) Fire timer #2 immediately
audioTimer.fire()
} // At the end of this method body:
// - Timer #2 was never invalidated
// - audioTimer no longer references Timer #2, but:
// - Timer #2's runloop still references it, keeping it alive
// - Timer #2 is leaked
// ... and will continue firing forever.
func audioPlayer(){
AudioServicesPlaySystemSound(1104)
}
We can see that a Timer is made in section one, which should fire off in timerSeconds seconds, 0. At section 2, that timer is invalidated. Even though the Timer was to fire off in 0 seconds, it is almost certain that its run loop hasn't gotten a chance to fire it yet. Thus, this time is created, never fires, and then invalidated. There's no reason for it to exist at all there.
Then, in section 3, Timer #2 is created and scheduled. it is manually fired at section 4, and then it's permanently leaked.
The solution
You need an instance variable that holds reference to the timer. Without this, you have no way of invalidating the timer that has been already scheduled.
Secondly, you need to invalidate the timer at the appropriate time.
I suggest you take a look at Rob's answer for an example.

Youre creating a new, infinitely repeating, timer once, invalidate it immediately (why?), and then create another (why?), which is leaked forever.

You are creating a new timer,invalidating then creating a timer again.
You could try creating the timer,and when calling the audioPlayer function,checking for which sound to play depending on the value of the timerSeconds variable.

Related

action does not wait till function call ends

I have 2 actions that i put in a sequence. In the first action I am calling a method to calculate the new waiting time for the next action. The next action is just a wait for this duration, but the second action always executes straight away, so the time must be 0. I debugged it and in the method spawnFlowers i get the time returned as 3.5 seconds.
these are my 2 actions
let spawnFlowerAction = SKAction.run {
self.WaitTime = self.calculateWaitingTime()
}
let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime)
I execute it this way:
let spawnSeq = SKAction.sequence([spawnFlowerAction, waitForNewFlower])
let spawnRepeat = SKAction.repeat(spawnSeq, count: 4)
self.run(spawnRepeat)
Result: 4 times spawned without waiting, printing 4 different calculated times in the console from the calculateWaitingTime function (in which the spawning happens)
What is a good way to fix this?
The problem is trying to dynamically change the values used within SKActions after the action has been created. For example when your WaitTime variable changes while running the spawnFlowerAction, the waitForNewFlower Action's wait time won't change dynamically because it doesn't reference WaitTime. Instead its wait value is whatever your variable WaitTime was when you declared let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime) (Which I'm guessing was initially 0). Same concept goes with your other two spawn actions.
I usually use the dispatch Queue for things like these, but to use SKActions here's a function. Just call it once and input the number of times you want it to repeat.
func spawnRepeat(count: Int) {
//Put whatever code to spawn flower here
print("SPAWN FLOWER")
if count > 1 {
//Recalculate WaitTime
WaitTime = calculateWaitingTime()
let waitAction = SKAction.wait(forDuration: WaitTime)
run(waitAction, completion: { self.spawnRepeat(count: count - 1) })
}
}

How can I remove the extra space that appears in Xcode 9 when defining inline documentation for a parameter?

I have defined some inline documentation in Xcode 9.4.1 like this:
/**
Initiates a timer that counts down to zero from a passed-in value defined in CXP. If the timer reaches zero, it executes a method to log out the user.
- parameter duration: An integer expressed in seconds that defines the time a user has to choose an action before being automatically logged out.
- returns: `doLogout()` only if the timer reaches zero.
*/
But when I view this documentation, there is a space in front of the parameter name duration:
Gabriel Theodoropolous wrote an informative post entitled Documenting Your Swift Code in Xcode Using Markdown in May 2016. In it, he provides a screen shot under the Keywords section where there is clearly no extra space– everything lines up nicely.
While our versions of Xcode differ (he was using Xcode 7), it seems crazy that if this is a bug, it would not have been fixed by now.
I have attempted to remove the space through a lot of trial and error but nothing has worked.
How can I remove the space in front of duration?
MORE CODE FOR REFERENCE:
class AutomaticLogoutTimer {
fileprivate var duration: Int = 0
fileprivate var timer: Timer = Timer()
fileprivate var disposeBag = DisposeBag()
/**
Initiates a timer that counts down to zero from a passed-in value defined in CXP. If the timer reaches zero, it executes a method to log out the user.
- parameter duration: An integer expressed in seconds that limits the time a user has to choose an action before being automatically logged out.
- returns: `doLogout()` only if the timer reaches zero.
*/
public func startTimer(duration: Int) {
self.duration = duration
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
self.duration -= 1
if(self.duration == 0) {
self.stopTimer()
return self.doLogout()
}
}
}
// a little more code here
}

Circular UIScrollView – SwiftCarousel: Want to autoscroll

I am using a third party for circular scrollview and Everything is perfect. Only thing that i want is scrollview should autoscroll so that images should be autoscroll after every 15 second. Third Party Link: https://github.com/DroidsOnRoids/SwiftCarousel
Q: I want to ask in Example 2 is there any way images should be autoscroll after every 15 second?
A:
Hey!
There is no built-in method that we've made, but you can use NSTimer that will do a task every 15 second, and the task would select the next item using method selectItem(:animated). You should be also aware that you might try to select to the index that is lower than 0 or index that is higher than the amount of items in carousel, so you have to be sure to make the conditional statements.
Cheers!
i found above in SwiftCarousel homepage (http://www.thedroidsonroids.com/blog/ios/circular-scroll-view-swiftcarousel/).
Edit:
//declaring variables
var carouselTimer : NSTimer!
var counter = 0
//
//in viewDidLoad
carouselTimer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: #selector(selectIteminCarousel), userInfo: nil, repeats: true)
//
func selectIteminCarousel()
{
carousel.selectItem(counter, animated: true)
counter += 1
if counter > 2
{
self.counter = 0
}
}
this piece of code makes carousel autoscroll.

Initialization issue in Swift 2

I was working on a game with Swift 1 and Xcode 6 and everything was working perfectly. But when I updated to XCode 7 and Swift 2, I was getting a weird issue. In my game I had some NSTimers saved in variables to spawn the enemies. An example of one of this variables was something like this:
var firstTimer = NSTimer.scheduledTimerWithTimeInterval(actualTime, target: self, selector: Selector("SpawnEnemy"), userInfo: nil, repeats: true)
When I updated, every variable of this type (I had like 4) got an issue that says: "Initialization of variable "firstTimer" was never used; consider replacing with assignment to "_" or removing it."
I can't just change every variable's name to "_", and I never had to initialize it.
What is going on? And why didn't this happened before?
btw: I do use this variables in the code.
The error says it all, you didn't use the value in your code at all. If you intend to never stop the timer, you can just omit the declaration of the variable like this:
NSTimer.scheduledTimerWithTimeInterval(actualTime, target: self, selector: Selector("SpawnEnemy"), userInfo: nil, repeats: true)
But if you want to stop it later you should probably save it:
class MyViewController {
var timer : NSTimer?
func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(actualTime, target: self, selector: Selector("SpawnEnemy"), userInfo: nil, repeats: true)
}
func stopTimer() {
timer?.invalidate()
}
}
What you can do:
var timer = ... gives a warning that you didn't use the value (if you didn't) and a warning that timer never changed and should be a let.
let timer = ... gives a warning that you didn't use the value (if you didn't)
let _ = ... same as _ = ... evaluates the right-hand side and discards the result. No warnings
Just ... does the same as the last one, but can give a warning on functions with the #warn_unused_result attribute when the result isn't used.

Does DidContactBegin Fire Multiple Times at the One Time

I have a node which makes contact with two other nodes at the same time. What I'm wondering is, when my node make contact with the other two nodes (at the same time), does both the didContactBegin code fire at the same time, or is one didContactBegin processed and then the next.
I would have figured that one fires and then the next, so to make the second or third didContactBegin not fire, I setup a boolean and a timer. But after doing this, I'm starting to think the didContactBegin fires both at the same time, as my code below seems to get double println messages ever so often, but not all the time.
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node?.name == "segment" || contact.bodyB.node?.name == "segment" {
if boolean == true {
boolean = false
timerResetBoolNextAction.invalidate()
timerResetBoolNextAction = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("ResetBoolean"), userInfo: nil, repeats: false)
println("boolean is true")
}
}
}
func ResetBoolean() {
boolNextAction = true
}
Anyway, the main thing I want to know is does the didContactBegin fire all at once or does it queue. Because if it does queue, I believe there must be something else wrong with my code.
it will only go in to the if statement if one "or" the other is true so if they both happen at the same time the if statement will read the first clause and it will be true and will go straight in and execute your code "once only". the statement would need to be true again on the next frame or loop for it to happen again.
Hopefully this answers your question.