Swift: applicationDidEnterBackground timer - ios

I need to add an NSTimer in the applicationDidEnterBackground method; I tried adding
NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: Selector("test"), userInfo: nil, repeats: true)
But it's not working.

According to your comments, your declaration should look something like this:
NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: "test:", userInfo: nil, repeats: false)
Note two things:
You can pass a string literal to the selector parameter directly (no need for Selector() syntax), but this is of course optional.
The NSTimer expects the selector that will fire when it hits zero to accept a parameter (of type NSTimer) to which it will pass itself, so a colon (:) is needed at the end of the selector name to indicate that it will take a parameter.
The selector should have the following signature: timerFireMethod:
(including a colon to indicate that the method takes an argument). The
timer passes itself as the argument, thus the method would adopt the
following pattern: - (void)timerFireMethod:(NSTimer *)timer
From the NSTimer documentation.
Because of this, you'll also have to modify your function declaration a bit:
func test(timer: NSTimer) {
// Proceed to grab location from background
}

Related

How to properly fire/call a "selector" in Swift?

Question Summary:
If you have a Swift class that takes a selector as an argument in its initializer, how do you manually "fire/call" that selector?
Full Question:
Consider the following attempt at making a custom timer in Swift:
let TIME_INTERVAL = 0.1
class ValueAnimator : NSObject {
private var timer = Timer()
private let maxRep: Int
private var currentRepIndex: Int = 0
private var selector: Selector
init(durationInSeconds: Int, selector: Selector) {
print("VALUEANIMATOR INIT")
self.maxRep = Int(Double(durationInSeconds) / TIME_INTERVAL)
self.selector = selector
}
func start() {
timer = Timer.scheduledTimer(timeInterval: TIME_INTERVAL, target: self, selector: (#selector(timerCallback)), userInfo: nil, repeats: true)
}
#objc func timerCallback() {
currentRepIndex += 1
perform(selector) // <-------- this line causes crash, "unrecognized selector sent to instance 0x600001740030"
print ("VA timer called!, rep: \(currentRepIndex)")
if currentRepIndex == maxRep {
timer.invalidate()
print("VA timer invalidated")
}
}
}
The usage of this "ValueAnimator" would be similar to a normal Timer/NSTimer, in that you pass a "selector" as an argument and that selector is called each time the ValueAnimator fires:
[In Parent Class]:
// { ...
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp))
valueAnimatorTest.start()
}
#objc func temp() {
print("temp VA callback works!") // this doesn't happen :(
}
I'm trying to implement the same thing and as I understand, the line:
perform(selector)
should fire the selector in the parent class, but instead I get the error: "unrecognized selector sent to instance 0x600001740030"
I'm in a bit over my head here. I have tried googling the error, but everyone seems to be talking about how to use a selector from the parent-side (how to use Timer.scheduledTimer(), etc.) but I already know how to do that successfully.
I've also tried various tweaks to the code (changing public/private, scope of variables, and different forms of the performSelector() function)... but can't figure out the proper way to make the selector fire... or the unrelated mistake I've made if there is one.
Thanks for any help.
By calling perform(selector) it's like you're calling self.perform(selector) (self is implied), and by doing so the current instance of the ValueAnimator class is the object that actually performs the selector. When that happens, it tries to call a method called temp() of the ValueAnimator class, but as it doesn't exist the app is crashing.
You can verify that if you add a temp() method in the ValueAnimator:
#objc func temp() {
print("Wrong method!!!")
}
If you run now you'll have no crash and the "Wrong Selector!!!" message will appear on the console.
The solution to your problem is to pass the object that should run the selector method along with the selector to the initialisation of the ValueAnimator object.
In the ValueAnimator class declare the following property:
private var target: AnyObject
Update the init method so it can get the target as an argument:
init(durationInSeconds: Int, selector: Selector, target: AnyObject) {
...
self.target = target
}
Also update the timerCallback():
#objc func timerCallback() {
...
_ = target.perform(selector)
...
}
Finally, when you initialise a ValueAnimator instance pass the object that the selector belongs to as well:
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp), target: self)
Run again and the proper temp() method will be executed this time.
I hope it helps.
You are calling perform on the wrong object: its an instance method of NSObject, so you are trying to call perform on ValueAnimator and ValueAnimator does not respond to "temp". You must pass in both the object and the selector you want to perform, then you call perform on that object with the selector. Notice that this is exactly what Timer does: you have to pass in self as the object and the timer call the selector you specify on self.

Function with Completion in Timer Selector

I am declaring a function as such:
#objc func fetchDatabase(completion: ((Bool) -> Void)? = nil)
I'm allowing the completion to be nil so I can either call it as fetchDatabase() or as
fetchDatabase(completion: { (result) in
// Stuff in here
})
However, I am also trying to use this function in a #selector for a Timer. I am creating this timer using the following line:
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fetchDatabase), userInfo: nil, repeats: true)
Without the completion, this runs fine. However, with the completion added, I get an EXC_BAD_ACCESS error whenever the timer is run. Some help with correctly constructing this selector would be greatly appreciated, if this is in fact the error.
The selector passed to the timer only allows one of two possible signatures
someFunction()
someFunction(someLabel someParameter: Timer)
You can't pass your fetchDatabase(completion:) function because it doesn't match either of the two legal selector signatures.
You need to pass a valid selector which in turn calls your fetchDatabase(completion:) function. For example:
#objc timerHandler() {
fetchDatabase(completion: { (result) in
// Stuff in here
})
}
use #selector(timerHandler) with your timer.
Default arguments get applied at the calling site, so you'll need to generate two separate methods (one of which calls the other):
func fetchDatabase() { fetchDatabase(callback:nil) }
func fetchDatabase(callback:()->()) {
...
}
Now your scheduledTimer call should work fine.

Swift 2.2 Selector with multiple arguments - actually passing in

I want to use a selector, but I need to pass in the arguments
I understand the syntax follows:
#selector(class.method(_:paramName:))
but i need to actually pass in parameters. How do I do this?
Here's my attempt:
exploreTap = UITapGestureRecognizer(target: self, action: #selector(MainViewController.showViewWithIdentifier(_:exploreView,id:"explore")))
You cannot pass parameters to selectors, selector is just a method name, nothing else. You are not calling the method so you cannot pass parameters.
Just call the necessary code inside your tap handler.
func onTap() {
MainViewController.showViewWithIdentifier(exploreView, id:"explore")
}
and then
UITapGestureRecognizer(target: self, action: #selector(onTap))

Deinit is not called when assign NSTimer

This is how I declare property in my subclass of UIViewController:
private weak var timer: NSTimer?
This is what I do in viewDidLoad():
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer", userInfo: nil, repeats: true)
And this is my deinit:
deinit {
timer?.invalidate()
timer = nil
}
Deinit is not called because of NSTimer. Do not make funny telling that inside NSTimer there is a strong reference to my controller:) How can I workaround this?
I think that #i_am_jorf gave you a good direction. If you look at the documentation of invalidate method you will find a full explanation why it is acting like that
This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.
So yes, retain cycle created if invalidate not called in an appropriate moment

Swift - NSTimer to loop through array with delay

I have an array, and I wish to set the text of a UILabel to an element of the array, and then after a second set the text as the next element of the array. Once the end of the array has been reached, it needs to then return to the start. I have tried completing this with a for loop that runs through the array, with a delay function inside the for loop, but this does not slow down the operation of the for loop itself. I have also tried using an NSTimer,
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
func update() {
var i = Int()
UIView.animateWithDuration(0.2, delay: 0.3, options: nil, animations: { () -> Void in
if i == connectionName.count - 1 {
i = 0
println(connectionName[i])
} else {
println(connectionName[i])
}
}, completion: { (finished: Bool) -> Void in
i = i+1
})
}
But I just get an error
2015-01-08 15:06:10.511 Tinder[585:10642] -[Tinder.TinderViewController update]: unrecognized selector sent to instance 0x7fae99ead3f0
2015-01-08 15:06:10.612 Tinder[585:10642] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Tinder.TinderViewController update]: unrecognized selector sent to instance 0x7fae99ead3f0'
Is this because the function is defined within the view did load method?
You asked
Is this because the function is defined within the view did load method?
That is indeed the problem. NSTimer uses Objective-C messaging to call the timer function, and
nested functions in Swift are not exposed as Objective-C methods.
You have to define update() as a top-level function within the view controller class.
Unfortunately, the compiler cannot warn you, because it does not "know" that the
string "update" in the selector corresponds to the update() function. (Unlike the Objective-C #selector(), Swift uses simple strings as selectors, and the compiler
cannot verify its existence).
If you explicitly annotate a nested function with #objc then you will get a
compiler error.

Resources