I tried to run UIButton every minute when app is minimized. Therefore, I wrote a scheduledTimer function to run the button every 1 minute in Appdelegate, but it doesn't work, the error shows developeLocationHistoryButton:]: unrecognized selector sent to instance 0x60000024ee80. Please give me some guidance. My code is shown below.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var backgroundTaskTimer: Timer! = Timer()
func doBackgroundTask() {
DispatchQueue.global(qos: .default).async {
self.beginBackgroundTask()
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
//Making the app to run in background forever by calling the API
self.backgroundTaskTimer = Timer.scheduledTimer(timeInterval: 60, target:self , selector: #selector(ViewController.HistoryButton(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.backgroundTaskTimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundTask()
}
}
func beginBackgroundTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(withName: "test7", expirationHandler: {
self.endBackgroundTask()
})
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
I added self.doBackgroundTask() in ApplicationDidEnterBackground, and in ApplicationWillEnterForeground, I added
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
The error you are getting means that you are trying to call a method on an object that doesn’t implement it. Your problem lies in this line
Timer.scheduledTimer(timeInterval: 60, target:self , selector: #selector(ViewController.HistoryButton(_:)), userInfo: nil, repeats: true)
And precisely with target: self, selector: #selector(ViewController.HistoryButton(_:)) part. self here is the instance of AppDelegate and you are teling the timer to call a method named HistoryButton(_:) on it. But that method is defined on a objects of type ViewController - which is clearly stated in the selector.
To resolve your issue you can either
Instantiate the ViewController and pass it as target - not recommend, as this is prone to side effects.
Copy the method to the AppDelegate and update your selector argument - better, but you have repeated code and will need to update it in both places if needed.
Extract the method to an outside class and use it in both places (the AppDelegate and ViewController)
You can't do what you are trying to do. If you request background time using UIApplication's beginBackgroundTask(withName:expirationHandler:) method you get 3 minutes, and after that your app will get suspended anyway. The best you'll be able to do is to have your app's timer run for 3 minutes.
There is a lot wrong with your code.
You try to add a Timer with a target of self and a selector of ViewController.HistoryButton(_:), but:
The current class is class AppDelegate, which does not have a method called HistoryButton(_:). (Also note that method names should start with lower-case letters.)
Another thing. This code:
var backgroundTaskTimer: Timer! = Timer()
Makes no sense. There's no reason to create a Timer in that code. Make it an optional and don't assign a timer to it:
var backgroundTaskTimer: Timer?
If you create a timer using scheduledTimer(), there's no need to then add it to the current run loop, so get rid of the lines that begins Runloop.current... and the line RunLoop.current.run()
Another thing: Why do you call self.endBackgroundTask() immediately after beginning your background task?
There are other things wrong with your code, but that's a start. It's fundamentally flawed. You're trying to tackle things you clearly don't fully grasp yet. I suggest slowing down and learning the things you're trying to do bit by bit.
Related
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 need to be able to invalidate and restart a timer from a couple of different functions.
In order to do this, I think I need to create my timer as a class variable.
When I created the timer in the local scope of a function, it worked fine, but I couldn't invalidate it from other functions.
However, when I moved it to a variable defined at the class level with the type: Timer, it fails to call the selector function. I don't get any errors.
I have this in viewDidAppear:
self.timer? = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(UploadViewController.readFromBean), userInfo: nil, repeats: true)
Here is how my selector is set up:
func readFromBean(){
print("reading from bean")
if myBean != nil{
myBean?.readAccelerationAxes()
myBean?.readTemperature()
myBean?.readScratchBank(1)
myBean?.readScratchBank(2)
myBean?.readScratchBank(3)
myBean?.readScratchBank(4)
myBean?.readScratchBank(5)
}
}
I saw you liked one of the comments, but thought I'd share the strategy I have used successfully. In this case, the Timer is in the AppDelegate.
The class looks like this:
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
// lots of stuff removed
var loadOrdersTimer: Timer?
// ... blah blah ... application specific methods
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if let loadOrdersTimer = self.loadOrdersTimer? {
loadOrdersTimer.invalidate()
self.loadOrdersTimer = nil
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
setOrderLoadingTimer()
}
func setOrderLoadingTimer() {
self.loadOrdersTimer = Timer.scheduledTimer(timeInterval: intervalSeconds, target: self, selector: #selector(self.loadOrders), userInfo: nil, repeats: true)
}
#objc func loadOrders() {
// do the work for which the Timer was scheduled
}
}
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.
I checked the existing posts on this topic and also googled it, but I am not able to identify my mistake or make this work for me. I have a function iterativeDeepening() inside the class ChessPlayer. After say 15 seconds I want to stop further iterations within the function. In the code below, the function "flagSetter" is never invoked. If I use NSTimer.fire() the function is invoked immediately and not after 15 seconds. I tried placing the flagSetter function before or after iterativeDeepening(). Either case does not work. What have I done incorrectly?
class ChessPlayer {
var timeoutFlag = false
//Code
func iterativeDeepening() {
***//variables and constants***
let timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: #selector(self.flagSetter), userInfo: nil, repeats: false)
***while minDepth <= maxDepth
{
// Loop iteration code
if timeoutFlag { break out of loop }
}***
}
#objc func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
}
The requirement:
computerThinking() is fired from GameScene from human move's action completion handler.
GameScene.computerThinking() invokes ChessPlayer.iterativeDeepening()
iterativeDeepening runs a while loop incrementing "depth". For each "depth" an optimal move at that depth is evaluated. Higher the depth, more detailed the evaluation.
after 15.0 seconds i want to break out of the while loop with the depth and optimal move available at that point of time.
I am a lover of Objective-c and never used Swift yet in my projects. Googling NSTimer Swift, I found out the following steps to implement NSTimer correctly.
we need to define our NSTimer. The first variable that we are going to need is a variable called timer of type NSTimer. We do this like:
var timer = NSTimer()
Start NSTimer timer:
timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target:self, selector:#selector(ChessPlayer.flagSetter(_:)), userInfo: nil, repeats: false)
And your method flagSetter should be defined as this:
func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
This will now surely work as I've made my very first app, just for this question, made in Swift. Check how I put my selector. You're right with the warnings by the way.
If you need more information about Selectors, check this thread: #selector() in Swift?
Here is your solution: define timer outside of the function so you can invalidate it from another function. Right now, your timer is defined inside a function so it can only be altered inside that function, but that is not what you want. Fix it by doing the following: right below var timeoutFlag = false put var timer = NSTimer(). Then inside your function iterativeDeepening() get rid of the let. Then it will all work!!
Here's what your code should be, adapted from Hasya's answer and your provided code.
class ChessPlayer {
// Declare timer and timeoutFlag
var timer = NSTimer()
var timeoutFlag = false
func iterativeDeepening() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: “timerEventOccured”, userInfo: nil, repeats: true)
}
func timerEventOccured() {
print("timerEventOccured was called.")
timeoutFlag = true
self.timer.invalidate()
}
}
override func viewDidUnload() {
super.viewDidUnload()
self.timer.invalidate()
}
}
I'm pretty new to swift and I have some questions about swift and even just basic OOP (so if you can, please be specific with your answers, thanks a lot!)
So I am making an app that has a timer component and the follow code snippets are from that timer class (and the view controller):
I have stop/start function and a deinit:
var timer = NSTimer()
...
func start(){
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "tick", userInfo: nil, repeats: true)
}
func stop(){
timer.invalidate()
}
....
deinit(){
self.timer.invalidate()
}
so my first question is, why do I need to call self.timer in deinit and start but not in stop? Also when does deinit get called and what does it do differently than the stop function (seems to me they both invalidate the NSTimer)?
There is also an initializer:
init (duration: Int, handler: (Int) -> ()){
self.duration = duration
self.handler = handler
}
and the initializer is called in the view controller:
private func timerSetUP(hr: Bool, duration: Int){
timer = Timer(duration: duration){
(elapsedTime: Int) -> () in
let timeRemaining = duration - elapsedTime
println("what is the eT: \(elapsedTime)")
let timeReStr = self.getHrMinSecLabels(hr, timeremaining: timeRemaining)
....}
My question is about the closure, elapsedTime is a property in Timer class and is it just getting passed into the closure in the view controller? Why is there no reference to the Timer class (like timer.elapsedTime)? And I don't really need this closure right? I can just have another function that does the same thing (or is this easier to get the elapsedTime using this closure)?
There is also a Tick function in my Timer class:
#objc func tick(){
self.elapsedTime++
self.handler(elapsedTime)
if self.elapsedTime == self.duration{
self.stop()
}
}
This is the selector for self.timer = NSTimer.scheduledTimerWithTimeInterval(), is a selector just a function that gets called every time the timer fires? And do I just need to give NSTimer.scheduledTimerWithTimeInterval() the string name of the selector function? Also why is there #objc, this just looks like a swift function to me?
Your questions are loaded. Let me try addressing them one at a time:
why do I need to call self.timer in deinit and start but not in stop
It's personal coding style. self.timer is the same as timer, assuming you don't have a local variable overriding the instance variable.
Also when does deinit get called and what does it do differently than the stop function?
deinit is called when the run time deallocates your object. If you timer is still running at that time, it needs to stop it first. stop just stops the timer but keep the object in memory.
My question is about the closure, elapsedTime is a property in Timer class...
You need to understand closure a bit. elapsedTime is a parameter of the closure/anonymous function. The Timer object passes its elapsedTime property to that anonymous function when the timer fires. Work the same if you rename it like this:
timer = Timer(duration: duration){
(t : Int) -> () in
let timeRemaining = duration - t
println("what is the eT: \(t)")
let timeReStr = self.getHrMinSecLabels(hr, timeremaining: timeRemaining)
....}
is a selector just a function that gets called every time the timer fires?
Yes. But you need to specify what object to call the function on (see the target parameter in the next question).
And do I just need to give NSTimer.scheduledTimerWithTimeInterval() the string name of the selector function?
Yes, something like this:
// Fire the tellTime() function of the current object every second (1000ms)
self.timer = NSTimer.scheduledTimerWithTimeInterval(
timeInterval: 1000,
target: self,
selector: "tellTime",
userInfo: nil,
repeats: true)
Also why is there #objc, this just looks like a swift function to me?
This is to make Objective-C code aware of your Swift function. You can skip it if you program exclusively in Swift.