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 :)
Related
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!
I use this code for stopping NSTimer
[timer invalidate]
timer = nil;
It works fine for the first run. But, after I resume the timer with this code.
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
NSTimer won't stop anymore with [timer invalidate]
It look like multiple instance of timer is running simultaneously. You can do one thing, before start to run a new timer, check for previous instance of timer, and if timer instance is available, then invalidate it. After this start new instance
if(timer)
{
[timer invalidate];
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
In apple's official document they said:
You must send this 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.
If your timer is running on main thread, do this:
[timer performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:YES];
If it is on any other thread, lets call the thread myThread, then do this:
[timer performSelector:#selector(invalidate) onThread:myThread withObject:nil waitUntilDone:NO];
Hope this helps.. :)
Just invalidate the timer inside the selector that fires. That will ensure you have a pointer to the correct timer (which is probably why your invalidate call isn't working:
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates:)
userInfo:nil
repeats:YES];
Note the colon after checkNewUpdates:
Then, in your checkNewUpdates: method, do something like this:
- (void)checkNewUpdates:(NSTimer*)timer
{
// do somehting
// Then, check if the criteria for stopping the timer has been met, and invalidate it here.
if( self.shouldStopTimer ) // made up variable, use your own criteria.
{
[timer invalidate];
}
}
I know this doesnt answer your question per-se;
Can I suggest using polling mechanism instead of a timer? Ive had a world of trouble with NSTimers in the past and polling was a good alternative. Ive made a simple one below.
- (void) doPoll {
// do logic here
if (shoudStop) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, X * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self doPoll];
});
}
This is just a simple example, it does not stop retain cycles If you choose to try this, Yours should.
Hope it helps.
self.timerProgress=[NSTimer scheduledTimerWithTimeInterval:50.0 target:self selector:#selector(stopProgressView) userInfo:nil repeats:NO];
-(void)stopProgressView
{
if ([self.timerProgress isValid]) {
[self.timerProgress invalidate];
self.timerProgress=nil;
}
}
and on a button click when i tried to invalidate NSTimer object
-(void)cancelTimer
{
if ([self.timerProgress isValid]) {
[self.timerProgress invalidate];
self.timerProgress=nil;
}
}
it don't get invalidate. It calls once stopProgressView after the interval of 50.
How to get resolve from this issue?
- (IBAction)stopTimer {
if ([timerProgress isValid]) {
[timerProgress invalidate];
}
}
Don't use self.timerProgress use just timerProgress
The most likely reason for this is that your timer scheduled on a different run loop to the one where you try and invalidate it.
Timers must be invalidated on the same thread/runloop as the run loop that they are scheduled on.
Cocoa touch isn't thread safe, so you should be running all UI related activities on the main thread. It may work if you do GUI work on different threads, but then you'll get random crashes, and you'll also generate timer problems like this.
It seems like from what you're posting it should work. This is how I have it in my apps and it works fine.
However, you could try making the selector one that takes a timer object like:
-(void)stopProgressView:(NSTimer *)timer{
//do stuff with timer here
}
Note that this would also mean that you should change #selector(stopProgressView) to #selector(stopProgressView:). Although for the record my current stop timer function just uses [self.timer invalidate] and it works fine.
My other piece of advice for debugging is to use NSLogs to make sure each of the methods are in fact getting called, and that when the method is called an NSLog within the if clause to make sure that works.
You create NSTimer with out NSRunLoop so your NSTimer not started, to add this code after
self.timerProgress = [NSTimer scheduledTimerWithTimeInterval:50.0
target:self
selector:#selector(stopProgressView)
userInfo:nil
repeats:NO];
//add
[[NSRunLoop currentRunLoop] addTimer:_tapTimer
forMode:NSDefaultRunLoopMode];
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) {
}
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.