Initialization issue in Swift 2 - swift

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.

Related

looped timer does not invalidate as instructed

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.

"Ambiguous reference to member 'scheduledTimerWithTimeInterval( _:invocation:repeats:)'"

I'm trying to set up a timer to run an event periodically, as described here. I get that the hashtag parameter has been deprecated, so I tried to rewrite the startTimer code accordingly:
func startTimer()
{
let theInterval: NSTimeInterval = 5.0
self.timer = NSTimer.scheduledTimerWithTimeInterval
(
interval: theInterval,
target: self,
selector: Selector("timerTick:"),
userInfo: "Hello!",
repeats: true
)
}
Problem is, I keep getting the error: "Ambiguous reference to member 'scheduledTimerWithTimeInterval( _:invocation:repeats:)'". But I'm not trying to run scheduledTimerWithTimeInterval( _:invocation:repeats:), I'm trying to run scheduledTimerWithInterval:target:selector:userInfo:repeats. I'd think that would be obvious from the parameters I'm passing.
What do I need to do differently?
There are two problems:
It is confused by the newline and whitespace between scheduledTimerWithTimeInterval and the open parentheses.
You should not supply the first parameter label.
So, you could do:
timer = NSTimer.scheduledTimerWithTimeInterval(
2.0,
target: self,
selector: #selector(timerTick(_:)),
userInfo: "Hello!",
repeats: true
)
Note, I also replaced the Selector("timerTick:") with the #selector syntax.

Unrecognized selector sent to instance: Swift 2 NSTimer setup

I've read the other posts. I've read the docs. I've read nearly everything I can get my hands on in regards to this issue, but it persists. To be clear, this problem ONLY happens if I want to capture the timer as a parameter in the receiving function.
Here's the error:
2016-03-07 18:26:29.000 AlarmClockAI[12286:3807622] -[AlarmClockAI.Alarm playAlarm]: unrecognized selector sent to instance 0x7fd0936289e0
I have a class derived from NSObject. In that class, I'm creating a series of timers like so:
timer = NSTimer(fireDate: fireDate, interval: 5.0, target: self, selector: Selector("playAlarm:"), userInfo: ["UUID":UUID], repeats: false)
NSRunLoop.currentRunLoop().addTimer(timer!, forMode: NSDefaultRunLoopMode)
func playAlarm(timer: NSTimer) {}
I still get the unrecognized selector error.
Other variations I've tried:
NSTimer(fireDate: fireDate, interval: 5.0, target: self, selector:"playAlarm:", userInfo: ["UUID":UUID], repeats: false)
Same error.
Other notes:
I'm running Swift 2.1.1
I realize I shouldn't need the #objc tag, but I've tried that too.
Clean + build + restart didn't help.
FML
Ugh. Sorry guys. I found the answer: I have more than one method that creates timers, and I was looking at the wrong one.

extra argument userinfo in call

Get the compile error message from xCode with Swift language: "extra argument userinfo in call". the question is how to use userInfo data from timer to argument "userInfo" in notification center.
func onSetAudioWithTrackIndex(CurrentTrackIndex:Int, urlAudio: String){
........
//try to pass currentTrackIndex data to timerfire
playTimeClock = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "onSetPlayProgressTime:", userInfo: CurrentTrackIndex, repeats: true)
}
//Timerfire
func onSetPlayProgressTime(timer: NSTimer){
var currentTime = mediaPlayer.currentPlaybackTime
var playDuration = mediaPlayer.duration
var trackIndexDict = ["trackIndex" : timer.userInfo]
................
if currentTime == playDuration{
NSNotificationCenter.defaultCenter().postNotificationName(MPMoviePlayerPlaybackDidFinishNotification, object: self, userInfo: trackIndexDict)
}
return
}
Swift can give you slightly strange compiler errors sometimes, which amount to “there were multiple possible options, none of them worked, so here’s what’s wrong with one of them”. When the one it complains about is not the one you were trying for, this can be very strange
This is why Swift tells you so often that something is “not a Int8” even though that has nothing to do with what you were trying to do (huh, I was trying to concatenate two strings, what have Int8s got to do with anything? – it’s because one of the possible options for the + operator is to work on Int8s so that’s the one it chooses to complain about).
In this case, postNotificationName has multiple overloaded versions, one with 1 argument, one with 2, and one with 3 (the one you want). None of them fit the types you’ve supplied so it’s saying “one of the options is a call with 2 arguments, and you supplied 3, so that won’t work, there’s an extra argument”.
Unfortunately this is really quite confusing and throws you off the scent of what is actually wrong. Assuming you cut and pasted your actual code, looks like there’s a spelling error in MPMoviePlayerPlaybackDidFinishNotification, and the userInfo argument label is missing a colon after it.
(p.s. you don’t need to explicitly put a return at the end of functions that return void, though it doesn’t do any harm)
In swift 3 , I got this same error. When I was converting the swift 2.2 to swift 3, as the syntax was changed so it throws this error.
Swift 3:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: MPMoviePlayerPlaybackDidFinishNotification), object: self, userInfo: trackIndexDict)
The problem is
that the userInfo property of NSTimer is an optional:
var userInfo: AnyObject? { get }
so that trackIndexDict has the type [String : AnyObject?] which is not convertible
to [NSObject : AnyObject] as expected by the last parameter of postNotificationName().
With optional binding you can "test and unwrap" the property:
if currentTime == playDuration {
if let timerInfo: AnyObject = timer.userInfo {
let trackIndexDict = ["trackIndex" : timerInfo]
NSNotificationCenter.defaultCenter().postNotificationName(MPMoviePlayerPlaybackDidFinishNotification,
object: self,
userInfo: trackIndexDict)
}
}
thanks, Martin, this(userInfo of NSTimer is optional) is root cause. with the below change. this can be fixed
if let timerUserInfo: AnyObject = timer.userInfo! {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: MPMoviePlayerPlaybackDidFinishNotification), object: self, userInfo: ["trackIndex":timerUserInfo])
}

Default Parameters

I have a function with one parameter. This parameter has a default value. (true)
func disableButton(animated: Bool = true) {
if (animated) {
...
} else {
...
}
}
So i can call the function as:
disableButton(animated: true)
or:
disableButton()
and they give me the same result.
Now I have an NSTimer running a selector on completion like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton"), userInfo: nil, repeats: false)
It will send the disableButton() message on completion. However on completion it causes the app to crash.
If i do:
func disableButton() {
disableButton(animated: true)
}
Then the timer successfully sends that message and the app does not crash.
Of course this is really ugly and kind of limits the great Swift feature of default parameters.
Is this a bug (that I should report) or am I doing it wrong?
Thanks for any help!
There's a difference between -disableButton and -disableButton: selectors. But you can't just use your desired selector like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton:"), userInfo: nil, repeats: false)
because NSTimer here assumes selector with no arguments or selector with single argument (which will be used to pass firing NSTimer instance).
I believe, you should migrate from using selectors, as you want to use all that great features Swift delivers, as far as selectors is outdated pattern.
Just a note: it could work if you use "disableButton:" selector, maybe NSTimer's pointer will be interpreted as true and maybe not, not sure what will happen (maybe it could crash due to Swift strong typing stuff). But depending on that behaviour is a bad practice which could lead to bugs which are extremely hard to exterminate.