Hi I am trying to stop an NSTimer by using "invalidate" however from everything that I have tried I cant seem to get the timer to stop. Here is the code that I have to make this work. I am trying to stop the timer from a different class.
My Timer
_tripTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateTimerLabel:)
userInfo:[NSDate date]
repeats:YES];
which is synthesized and is strong
and the stopping method:
-(void)stopTimer
{
[_tripTimer invalidate];
}
and in my other class to get it to stop I am doing this:
[_carTripViewController stopTimer];
however that is not working. It is performing the method but not stopping the timer. Im not sure if i am creating a new instance and that is why it is not working. How can I get it to invalidate from another class?
Thank you! I am fairly new to objective-c and not sure how to access it
In the docoumentation about the invalidate method Apple says:
Special Considerations
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 you create the thread in the main method you can stop it in the main method by calling:
[self performSelectorOnMainThread:#selector(myMethod:)
withObject:anObj waitUntilDone:YES];
in your case something like:
[_carTripViewController performSelectorOnMainThread:#selector(stopTimer:)
withObject:nil waitUntilDone:YES];
I see two most probable causes:
1) You send stopTimer message to a different object of your class, not the one which where the timer has been launched.
2) _tripTimer variable doesn't point to the timer object any more, it points to somewhere else, probably to nil.
I had a similar problem and what I did was to add the timer to my appDelegade and use that as my timer context. Im not sure if this is academically 100% correct, but it works for me and is a workable hack at least. So far I haven't run into any problems and my app has been used extensively. See my code example:
if (!self.pollerTimer) {
self.pollerTimer = [NSTimer scheduledTimerWithTimeInterval:POLLER_INTERVAL
target:self
selector:#selector(performPollinginBackground)
userInfo:nil
repeats:YES];
//adds the timer variable and associated thread to the appDelegade. Remember to add a NSTimer property to your appDeledade.h, in this case its the pollerTimer variable as seen
NUAppDelegate *appDelegate = (NUAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.pollerTimer = self.pollerTimer;
}
Then when I want to stop the timer from anywhere in my app I can do the following:
NUAppDelegate *appDelegate = (NUAppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.pollerTimer) {
[appDelegate.pollerTimer invalidate];
appDelegate.pollerTimer = nil;
}
Related
In my app I have a following piece of code:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
repeats:YES
block:^(NSTimer * _Nonnull timer)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf pingWithBlock:nil];
}];
this works perfectly in iOS 10+, but I need the app to support iOS 9 as well. So I needed to provide a method that would work for both.
I tried this:
__weak __typeof(self)weakSelf = self;
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:weakSelf
selector:#selector(pingWithBlock:)
userInfo:nil
repeats:YES];
pingWithBlock method is defined in the same class, it's an instance method.
But this doesn't seem to work, meaning I get a bad memory access crash.
If anyone has any suggestions it will be highly appreciated.
EDIT:
thanks to #dgatwood explanations code below fixes the issue
- (void)autoPing
{
_pingTimer = [NSTimer scheduledTimerWithTimeInterval:self.autoCheckInterval
target:self
selector:#selector(pingWithBlock)
userInfo:nil
repeats:YES];
}
-(void)pingWithBlock
{
[self pingWithBlock:nil];
}
This is kind of odd. NSTimer retains its target. Maybe that doesn't happen in this case because of the __weak, but I thought it did anyway. *shrugs*
Either way, this sounds like a multithreading race condition:
Your timer isn't retaining the object, so it could go away at any time.
Something else is retaining the object.
The timer is scheduled in the runloop of the thread that was running when the timer was constructed.
That something else disposes of the reference to the object in another thread.
The timer fires in the first thread and the zeroing weak reference hasn't zeroed because the object is still halfway through destroying itself.
A crash occurs.
The best fix is to let the timer retain the target object (by removing all the weakSelf stuff). If the timer is a repeating timer, provide a method to allow the code that disposes of the enclosing object to cancel that timer, and be careful to always call it.
I cannot seem to work this one out. Here is my set up:
I have a function called requestDataWithCompletion:(someBlock)block. I call it when the class is initialised. The function requests certain motion data. I want to do this periodically, therefore, the first time I call this function, I specify some completion code which sets up a timer that re-calls this function periodically. The timer calls it via another function requestDataWithoutCompletion which simply calls the requestDataWithCompletion but with an empty block (so I don't keep creating timers);
- (void) requestDataWithCompletion:(someBlock)block {
// BREAK POINT 1
[self.motionManager queryActivityStartingFromDate:start toDate:[NSDate date] toQueue:self.queue withHandler:^(NSArray *activities, NSError *error) {
// BREAK POINT 2
// do some processing;
block();
}];
}
The block simply creates a timer on the main queue, which periodically recalls this function, but with no completion (since I don't want to keep creating more timers).
block = ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
});
}
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion^{;}];
}
The amazing thing is that despite this set up, my app is creating timer after timer! I can't understand why.
I have placed break points in requestDataWithCompletion method. One is outside the block submitted to NSOperationQueue to get activity data (BREAKPOINT 1) and one is inside the block submitted to NSOperationQueue. (BREAKPOINT 2). Basically it shows that each time the method is called by the timer, BREAKPOINT 1 has an empty completion block (as it should be) but then strangely BREAKPOINT 2 has the completion block I submitted when I first called the function when initialising the class. Therefore, it continues to create a new timer each time the function is called by the timer. Over time, this means a massive number of timers and then the app crashes!
I have a feeling this is something to do with NSOperationQueue, but I really don't know what it could be.
In your initialisation (or when you first want to get the data and then continue getting it):
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
[self.timer fire]; //get the activity data immediately.
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion:^{}];
}
With your original requestDataWithCompletion: method. (though you could get rid of requestDataWithCompletion: and put it's code directly in requestDataWithoutCompletion if you're not using it elsewhere)
I have the following pair of functions in a MessagePlayerViewController(UIViewController) which move a slider to reflect playback progress of an AVAudioPlayer:
-(void)startTrackingPlayback
{
if(!self.isPlaying)
{
self.isPlaying = YES;
self.playbackTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateProgress) userInfo:nil repeats:YES];
}
}
-(void)stopTrackingPlayback
{
if(self.playbackTimer)
{
if(self.playbackTimer.isValid)
{
[self.playbackTimer invalidate];
self.playbackTimer = nil;
}
}
self.isPlaying = NO;
}
Intermittently, and following no discernible pattern, I get an Exec Bad Access ith the top two items in the stack as:
0 objc_msgSend
1 [MessagePlayerViewController stopTrackingPlayback];
How can this be? I check if the timer exists before I call isValid and I check isValid before I invalidate it.
Using a breakpoint I can see that the timer does exist, but the error occurs when I set it to nil. If I remove this line, I get an identical error on the line:
[self.playbackTimer invalidate];
I would suggest inspecting the way you use your MessagePlayerViewController. It seems to me that both the stack trace and the behaviour you describe hint at the fact that it is the controller that is being deallocated earlier than your timer.
Take into account the fact that the run loop where the timer is scheduled will keep the timer alive.
Maybe the fix is as simple as calling invalidate in your controller's dealloc method (or somewhere else where it makes sense), but if you do not provide more code, it is not possible to say.
Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.
Is there any method (like – viewDidLoad) to continuously execute a part of code? I need to be able to check a value on a remote server continuously.
The way you would do this is to set up an NSTimer.
-(void)startCheckingValue
{
mainTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(checkValue:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:mainTimer forMode:NSDefaultRunLoopMode];
}
-(void)checkValue:(NSTimer *)mainTimer
{
//Placeholder Function, this is where you write the code to check your value on the remote server
}
The timerWithTimeInterval function is the one that you are interested in, the main things that you need to pass it, as you see above, are the interval at which it will execute the function whose selector you pass it. The time interval is in seconds, so it is currently set to check every second, which is probably way too fast.
Use an NSTimer to execute the same block of code every x seconds. However, I don't think that's what you want, given that it would put a lot of extra load on the server and you might get banned, so there's probably a better way.
apple's page on NSTimer use
You ned to use NSTimer For this:
in your interface declare a NSTimer object like:
NSTimer *timer;
in your .m viewDidLoad method add the below line.
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:YES];
In the timerFireMethod method you need to do the server calling and other stuffs.