Invalidate NSTimer after VC is changed - ios

I am having a NSTimer in my VC and its working fine its sending location of user and i cant invalidate it on viewDidDisappear as i need to send in background also.
myTimer=[NSTimer scheduledTimerWithTimeInterval:2.0 target: self
selector: #selector(sendDataToSocket) userInfo: nil repeats: YES];
But the issue is when i instantiate same VC again it start the NSTimer again and 2 timers are working. so how can i stop the previous one or any other solution
Any help will be appreciated
Thanks

you should handle the timer instantiation mechanism in your AppDelegate
so, in your ViewController, every time you instantiate it, you can call
[((YourAppDelegate*)[UIApplication sharedApplication].delegate) startMyTimer:self];
where startMyTimer could be defined like this
-(void)startMyTimer:(id)aTarget
{
if(myTimer)
{
[myTimer invalidate];
myTimer = nil;
}
myTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: aTarget
selector: #selector(sendDataToSocket)
userInfo: nil
repeats: YES];
}
so you are sure that only one myTimer is always active in your app and you can call your ViewController's sendDataToSocket

You can use the singleton class or simple use the appDelegate class to just declare the myTimer object in appDelegate and use it in your viewController.
This will have a single object of timer and you dont need to create multiple objects and your ultimate objective will be resolved. Hope this Helps!

Related

iOS Thread Loop Logic

Im a little confused on the logic of creating a loop on a thread that is continuous . Just need a point in the Right Direction.
GCD or NSOpereation?
I have this JSON file that gets updated from a web job every 5 minutes.
Is there a way I can run a thread in the background that constantly checks the JSON for changes every N minutes or seconds ?
I', thinking i could use, just cant figure how I would implement
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC),
I would recommend using NSTimer with repeats: true:
override func viewDidLoad() {
super.viewDidLoad()
let _ = NSTimer.scheduledTimerWithTimeInterval(300, target: self, selector: Selector("checkForJSONChanges"), userInfo: nil, repeats: true)
}
func checkForJSONChanges() {
...
}
Or if you're using Objective-C:
- (void) viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:300.0f target:self selector:#selector(checkForJSONChanges) userInfo:nil repeats:YES];
}
- (void) checkForJSONChanges {
...
}

NSTimer doesn't stop with invalidate

I add timer like this
tim=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(repeatTim) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:tim forMode:NSDefaultRunLoopMode];
tim it is NSTimer property of my class.
Then i stop it on button click like
[[fbt tim] invalidate];
[fbt setTim:nil];
fbt it is instance of my class.
if i call only invalidate then it doesn't stop, but if i set it to nil then i got EXC_BREAKPOINT
here code of repeatTim method in selector
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
[appDelegate.wbv stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"intal()"]];
I tried to call init and invalidate in
dispatch_async(dispatch_get_main_queue(), ^{})
it also doesn't stop timer.
You have more than one timer running . Try this:
-(void)startTimer{
[self.myTimer invalidate]; // kill old timer
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[self.myTimer invalidate];
self.myTimer=nil; //set pointer to nil
}
Read documentation for NSTimer:
There are three ways to create a timer:
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
Allocate the timer and initialize it using the initWithFireDate:interval:target:selector:userInfo:repeats: method. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
You are using method which already adds it to mainLoop from 1. - you need to remove this line or create a timer with 2. approach and leave manual adding.
Also remember that you must send invalidate message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
I have tried every possible solution found but not able to resolve that at the end I have set repeat "false" while initialising timer like below
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.methodname), userInfo: nil, repeats: false)
And need to add above line in my selector method for whatever the condition for which I wanted to repeat the time.
For example:- My requirement is I want to repeatedly call some method until one condition satisfied. So instead of adding repeats true I set it false as repeat true does not invalidate timer in my case.
I have added below in my viewdidload method
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)
in selector function I added below code:-
func method{
if condition matched{
// here your timer will be invalidated automatically
}
else{
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,selector: #selector(viewcontroller.method), userInfo: nil,repeats: false)
}
}
Hope this will solve your problem.
Happy Coding :)

NSTimer timerWithTimeInterval: not working

I've created a test application with timer before implementing it in my project.
It was the first time I'm using timer.
But the issue is when I implemented timer using [NSTimer timerWithTimeInterval: target: selector: userInfo: repeats: ]; , it is not working.
Here is my code,
Interface:
#interface uialertViewController : UIViewController
{
NSTimer *timer;
}
-(void)displayAlert;
-(void)hideandview;
#end
Implementation:
#implementation uialertViewController
- (void)viewDidLoad {
[self displayAlert];
[super viewDidLoad];
}
-(void)displayAlert{
timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(hideandview) userInfo:nil repeats:NO];
alert = [[UIAlertView alloc] initWithTitle:#"testing" message:#"hi hi hi" delegate:nil cancelButtonTitle:#"continue" otherButtonTitles:nil];
[alert show];
[alert release];
alert = nil;
}
-(void)hideandview{
NSLog(#"triggered");
[alert dismissWithClickedButtonIndex:0 animated:YES];
[alert release];
[self displayAlert];
}
#end
Then I Changed [NSTimer timerWithTimeInterval: target: selector: userInfo: repeats: ]; with [NSTimer scheduledTimerWithTimeInterval: target: selector:userInfo: repeats: ]; , It is working. What was the issue with timerWithTimeInterval: ? Am I mising anything in my first implementation ? Thanks in advance.
scheduledTimerWithTimeInterval:invocation:repeats: and scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: create timers that get automatically added to an NSRunLoop, meaning that you don't have to add them yourself. Having them added to an NSRunLoop is what causes them to fire.
With timerWithTimeInterval:invocation:repeats: and timerWithTimeInterval:target:selector:userInfo:repeats:, you have to add the timer to a run loop manually, with code like this:
[[NSRunLoop mainRunLoop] addTimer:repeatingTimer forMode:NSDefaultRunLoopMode];
Other answers on here suggest that you need to call fire yourself. You don't - it will be called as soon as the timer has been put on a run loop.
Also one may want to make sure to add timer on the main thread.
assert(Thread.isMainThread)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(YourSelector), userInfo: nil, repeats: true)
As a previous answer noted schedule on the main thread, but rather than using assert, put it on the main thread:
#objc func update() {
...
}
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
And if async is not desired, try this:
let schedule = {
self.timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
if Thread.isMainThread {
schedule()
}
else {
DispatchQueue.main.sync {
schedule()
}
}
The difference between the two is that the timerWithTimeInterval method returns a NSTimer object that has not yet been fired. To fire the timer you have to use [timer fire]; On the other hand the scheduledTimerWithTimeInterval returns an NSTimer that has already been fired.
So, in your first implementation you were just missing [timer fire];

How to trigger NSTimer right away?

I need to call a method when my app starts and then call the same method every 5 seconds.
My code is pretty simple:
// Call the method right away
[self updatestuff:nil]
// Set up a timer so the method is called every 5 seconds
timer = [NSTimer scheduledTimerWithTimeInterval: 5
target: self
selector: #selector(updatestuff:)
userInfo: nil
repeats: YES];
Is there an option for the timer so it's trigger right away and then every 5 seconds ?
NSTimer's -fire, [timer fire]; is what you're looking for.
That will fire/immediately call the timer dismissing the time delay.
You can't schedule the timer and fire the event immediately all in the same line of code. So you have to do it in 2 lines of code.
First, schedule the timer, then immediately call 'fire' on your timer:
timer = [NSTimer scheduledTimerWithTimeInterval: 5
target: self
selector: #selector(updatestuff:)
userInfo: nil
repeats: YES];
[timer fire];
When fire is called, the time interval that your timer waits to fire will reset because it just ran, and then will continue to run thereafter at the designated interval--in this case, every 5 seconds.
In Swift:
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(foo), userInfo: nil, repeats: true)
timer.fire()
You can initialise the timer and fire it in the same line:
[(checkStatusTimer = [NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector:#selector(checkStatusTimerTick:) userInfo:nil repeats:YES]) fire];
In swift this can be achieved in one line like:
Timer.scheduledTimer(withTimeInterval: intervalInSec, repeats: true) {
}

Run repeating NSTimer with GCD?

I was wondering why when you create a repeating timer in a GCD block it doesen't work?
This works fine:
-(void)viewDidLoad{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
}
-(void)runTimer{
NSLog(#"hi");
}
But this doesent work:
dispatch_queue_t myQueue;
-(void)viewDidLoad{
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
});
}
-(void)runTimer{
NSLog(#"hi");
}
NSTimers are scheduled on the current thread's run loop. However, GCD dispatch threads don't have run loops, so scheduling timers in a GCD block isn't going to do anything.
There's three reasonable alternatives:
Figure out what run loop you want to schedule the timer on, and explicitly do so. Use +[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:] to create the timer and then -[NSRunLoop addTimer:forMode:] to actually schedule it on the run loop you want to use. This requires having a handle on the run loop in question, but you may just use +[NSRunLoop mainRunLoop] if you want to do it on the main thread.
Switch over to using a timer-based dispatch source. This implements a timer in a GCD-aware mechanism, that will run a block at the interval you want on the queue of your choice.
Explicitly dispatch_async() back to the main queue before creating the timer. This is equivalent to option #1 using the main run loop (since it will also create the timer on the main thread).
Of course, the real question here is, why are you creating a timer from a GCD queue to begin with?
NSTimer is scheduled to thread’s runloop. In code of question, runloop of thread dispatched by GCD is not running. You must start it manually and there must be a way to exit run loop, so you should keep a reference to the NSTimer, and invalidate it in appropriate time.
NSTimer has strong reference to the target, so target can't has strong reference to timer, and runloop has strong reference to the timer.
weak var weakTimer: Timer?
func configurateTimerInBackgroundThread(){
DispatchQueue.global().async {
// Pause program execution in Xcode, you will find thread with this name
Thread.current.name = "BackgroundThreadWithTimer"
// This timer is scheduled to current run loop
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
// Start current runloop manually, otherwise NSTimer won't fire.
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
}
#objc func runTimer(){
NSLog("Timer is running in mainThread: \(Thread.isMainThread)")
}
If timer is invalidated in future, pause program execution again in Xcode, you will find that thread is gone.
Of course, threads dispatched by GCD have runloop. GCD generate and reuse threads internally, there threads are anonymous to caller. If you don't feel safe to it, you could use Thread. Don't afraid, code is very easy.
Actually, I try same thing last week and get same fail with asker, then I found this page. I try NSThread before I give up. It works. So why NSTimer in GCD can't work? It should be. Read runloop's document to know how NSTimer works.
Use NSThread to work with NSTimer:
func configurateTimerInBackgroundThread(){
let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil)
thread.name = "BackgroundThreadWithTimer"
thread.start()
}
#objc func addTimerInBackground() {
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
This is a bad Idea. I was about to delete this answer, but I left it here to avoid others from doing the same mistake I did.
Thank you #Kevin_Ballard for pointing at this.
You'd only add one line to your example and it'd work just as you wrote it:
[[NSRunLoop currentRunLoop] run]
so you'd get:
-(void)viewDidLoad{
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run]
});
}
Since your queue myQueue contains a NSThread and it contains a NSRunLoop and since the code in the dispatch_async is run the the context of that NSThread, currentRunLoop would return a stopped run loop associated with the thread of your queue.

Resources