NSTimer "target": How to reference a method of a class? - ios

I'm trying to wrap my head around Swift. Currently I don't understand how this piece of code should work:
backgroundTimer = NSTimer.scheduledTimerWithTimeInterval(3, target: GameViewController(), selector: "addNext", userInfo: nil, repeats: true)
This statement crashes the game with
MyApp.GameViewController addNext]: unrecognized selector sent to instance 0x7b1cd0b0'
Basically I have a method in GameViewController class that I would like to call from all scenes with a timer. How can I reference gameViewController.addNext()? Basically what should I put in "target" ?
Here's the GameViewController and addNext() method:
class GameViewController: GAITrackedViewController, AVAudioPlayerDelegate {
...
func addNext() {
...
}
}

When you pass GameViewController() for the target, Swift creates a temporary instance of GameViewController, and gives it to NSTimer as the target for the addNext call. This is most certainly not what you want: you need a call to be made on the instance of your view controller, not on some temporary instance.
If you make timer registration from a method of GameViewController, say, from viewDidLoad, then you can pass self for the target parameter:
override func viewDidLoad() {
super.viewDidLoad()
backgroundTimer = NSTimer.scheduledTimerWithTimeInterval(
3
, target: self
, selector: #selector(GameViewController.addNext)
, userInfo: nil
, repeats: true
)
}

Related

Swift - Timer not able to access updated instance property

Im my react native project's native module I need to send some data periodically from Objective-C to Swift so I am using NSNotificationCenter. I receive the data successfully in my Swift class, inside the function attached to the observer, and I store it in a property.
If I access this property from any instance method call I can see that the value has updated.
However if I access the same property in the selector function attached to the Timer it appears as if the value has not been updated and I cannot figure out why? It seems as if the timer selector function does not have access to anything except the initial value of the property - I have also tried passing the property as part of userInfo to the Timer but the issue is the same.
[[NSNotificationCenter defaultCenter] postNotificationName:#"stateDidUpdate" object:nil userInfo:state];
class StateController {
var state: Dictionary<String, Any> = Dictionary()
var timer: Timer = Timer()
func subscribeToNotifications() {
NotificationCenter.default.addObserver(
self, selector: #selector(receivedStateUpdate),
name: NSNotification.Name.init(rawValue: "stateDidUpdate"), object: nil)
}
#objc func receivedStateUpdate(notification: NSNotification) {
if let state = notification.userInfo {
self.state = (state as? Dictionary<String, Any>)!
print("\(self.state)") // I can see that self.state has been updated here
}
}
func runTimer() {
self.timer = Timer(timeInterval: 0.1, target: self, selector: #selector(accessState(timer:)), userInfo: nil, repeats: true)
self.timer.fire()
RunLoop.current.add(self.timer, forMode: RunLoop.Mode.default)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 2))
}
#objc func accessState(timer: Timer) {
print("\(self.state)") // state is an empty Dictionary when accessed here
}
func printState() {
"\(self.state)" // value printed is the updated value received from the notification
}
}
I figured out that multiple instances of my Swift class being created was causing the issue. I assumed that React Native would create a singleton when calling a native module but it appears multiple instances are created as I could see how many times the init method was called. Switching to a Singleton pattern resolved the issue for me following this excellent video and this excellent gist on how to create a singleton in a react native project
class StateController {
static let shared = StateController()
private override init() {
}
}

I keep getting errors in my code, specifically "Type 'FirstViewController' has no member" and "Use of unresolved identifier"

I get errors saying, "Type 'FirstViewController' has no member 'keepTimer'" and "Use of unresolved identifier 'keepTimer'".
What am I doing wrong? How should I change this?
My main goal is to have a stopwatch start keeping track of time. When I press Save, it should add the stopwatch value as an event in Calendar.
I have made sure all are identical. They are identical in terms of spelling, but some have () at the end. When I add () at the end, I still get errors.
#IBAction func startButton(_ sender: Any) {
captureStartDateTime()
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(FirstViewController.self.keepTimer), userInfo: nil, repeats: true)
keepTimer()
startOutlet.isHidden = true
} ...
func keepTimer() {...}
#IBAction func startButton(_ sender: Any) {
captureStartDateTime()
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(keepTimer), userInfo: nil, repeats: true)
startOutlet.isHidden = true
}
#objc func keepTimer() {
}
In Swift, to use a method in a #selector(), the method must be marked with #objc
In your case you should be getting an error saying, Argument of '#selector' refers to instance method 'keepTimer()' that is not exposed to Objective-C
All you have to do is add #objc to expose this instance method to Objective-C.
i.e. your code should look like #objc func keepTimer() {...}
Is the keepTimer() in the FirstViewController? If not, the Xcode may cannot recognise the function

NSTimer scheduledTimerWithTimeInterval - not calling funciton

I want to run a timer in the background. So I created a singleton.
The problem is that after the set 5.0 seconds, it does not call the function timeEnded(). Xcode proposes to add #Objc in front of the function (like this: #Objc func timeEnded() {...) to solve some problem (I don't get what, though). But it still doesn't call that function. Any ideas?
class TimerService {
static let instance = TimerService()
var internalTimer: NSTimer?
func startTimer() {
guard internalTimer != nil else {
return print("timer already started")
}
internalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(TimerService.timeEnded), userInfo: nil, repeats: false)
}
func timeEnded() {
//NSNotificationCenter.defaultCenter().postNotificationName("timerEnded", object: nil)
print("timer Ended")
}
}
You never actually start the timer because your startTimer() function will always return before reaching the line of code where you create the timer.
In your guard statement you only continue the execution of the function if internalTimer != nil but the only place where you set the timer is after that statement. Thus, your timer is never created and internalTimer will always be nil.
This should fix your problem:
func startTimer() {
guard internalTimer == nil else {
return print("timer already started")
}
internalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(TimerService.timeEnded), userInfo: nil, repeats: false)
}
Selectors are a feature of Objective-C and can only be used with methods that are exposed to the dynamic Obj-C runtime. You cannot have a selector to a pure Swift method.
If your class inherits from NSObject then its public methods are exposed to Obj-C automatically. Since your class does not inherit from NSObject you have to use the #objc attribute to indicate that you want this method exposed to Obj-C so that it may be called with an Obj-C selector.
#selector() is the new syntax in Swift 2.2. It allows the compiler to check that the selector you're trying to use actually exists. The old syntax is deprecated and will be removed in Swift 3.0.

Swift- NSTimer crashing app

In Xcode I have this:
import UIKit
import AVFoundation
import Foundation
var position = 0
var gameTimer = NSTimer()
class ViewController: UIViewController {
#IBAction func button(sender: AnyObject) {
gameTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "runTimedCode:", userInfo: nil, repeats: true)
func runTimedCode() {
position = Int(arc4random_uniform(11))
}}}
When I run this app it crashes and returns the error: Thread 1: signal SIGABRT.
I have run the script without the NSTimer and it works perfectly.
I have also run it with and without the colon and it returns the same result.
Two issues:
Put func runTimedCode() out of the scope of #IBAction button(). Selector / target methods must be on the top level of the class.
Either remove the colon of runTimedCode: or declare runTimedCode as runTimedCode(timer: NSTimer). Each colon in a Selector represents one parameter.
You have a couple of issues:
You have defined your runTimedCode function inside your button function rather than as an instance function
You have specified the correct selector signature runTimedCode: (with the colon) but you have not specified the NSTimer argument that will be sent to this function (which is what is indicated by the : in the selector). You want:
import UIKit
import AVFoundation
import Foundation
var position = 0
var gameTimer : NSTimer? // Don't assign a value just to keep the compiler happy - if the variable is an optional declare it as such
class ViewController: UIViewController {
#IBAction func button(sender: AnyObject) {
self.gameTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "runTimedCode:", userInfo: nil, repeats: true)
}
func runTimedCode(timer:NSTimer) {
position = Int(arc4random_uniform(11))
}
}

How to implement callback/selector with performSelector in swift?

I am trying to create an equivalent of below method signature (Objective-C) in swift language. I couldn't get an answer on how to get the right equivalent for this. Any help is highly appreciated.
- (void)myMethod:(MyObject*)firstParam
setCallbackObject:(id)obj
withMySelector:(SEL)selector {
[obj performSelector:selector withObject:nil afterDelay:0]
}
First:
NOTE
The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
If you still want to implement it that way, read below.
You could use NSTimer:
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: self, selector: "selectorMethod", userInfo: nil, repeats: false)
String can be used where Selector is needed. It will automatically be converted (autoboxing).
The delay can be of course higher: 0.1 is then equal to 1 tenth of a second.
To call a method like:
func selectorMethod() {
...
}
We need to check before using the selector on the class. But the respondsToSelector: is in the NSObject protocol, so you have to derive at least from that (or one that subclasses from it).
To make it clear, here is the example.
Code:
class Test {
func myMethod(firstParam: String, setCallbackObject obj: AnyObject, withMySelector selector: Selector) {
if obj.respondsToSelector(selector) {
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: obj, selector: selector, userInfo: nil, repeats: false)
myTimer.fire()
} else {
println("Warning: does not respond to given selector")
}
}
}
class Test2: NSObject {
func selectorMethod() {
print("worked")
}
}
var test: Test = Test()
var callBackObj: Test2 = Test2()
test.myMethod("thisfirstis", setCallbackObject: callBackObj, withMySelector: Selector("selectorMethod"))
Output:
workedProgram ended with exit code: 0

Resources