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.
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)
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;
}
I am trying to figure out how to initiate a method call ever hour while my app is running.
I have created the following method which is started once the ViewController is loaded but the problem is, is that it runs once but then never again..
This is what my code looks like.
#define kMaxAliveTimeSeconds 5.0
// currently only running the query every 5 seconds for testing
- (void)deviceTimer {
if (!isAliveTimer) {
isAliveTimer = [NSTimer scheduledTimerWithTimeInterval:kMaxAliveTimeSeconds
target:self
selector:#selector(deviceIsAlive)
userInfo:nil
repeats:NO];
}
else {
if (fabs([isAliveTimer.fireDate timeIntervalSinceNow]) < kMaxAliveTimeSeconds-0.5) {
[isAliveTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kMaxAliveTimeSeconds]];
}
}
}
- (void)deviceIsAlive {
// do stuff here
}
the only thing is deviceIsAlive is called once but then never again.
Either set repeats:YES in the timer allocation statement (so it repeats automatically, otherwise nstimer invalidates itself after the first time) or in your deviceIsAlive function allocate a new timer.
I have a view-based template app and have UILabel & UIButton. For debugging purposes I'm showing and hiding the button whilst changing the UILabel.text.
In C++ I would 'thread root();' to execute the root method but I don't know how to in Objective-c. How to run my 'root' method once the view loads?
-(void) root
{
[bombButton1 setHidden:NO];
int s = 0;
int j = 10;
while ( s < j )
{
[bombButton1 setHidden:YES];
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
s++;
}
Edit:
Right, now I have: (but I get ERROR: Expected method body on the "-(void) rootMethod: NSTimer * timer {" line)
-(void) applicationDidFinishLaunching : (UIApplication *) application {
spawnTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(rootMethod:) userInfo:nil repeats: YES];
}
-(void) rootMethod: NSTimer * spawnTimer {
int s = 0;
int j = 10;
while ( s < j )
{
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
//debugLabel.text=#"debug test complete";
s++;
}
}
Several ways to do this, I think. Here's one:
[self performSelectorInBackground:#selector(root) withObject:nil];
You'd make this call in say, your -(void)viewDidAppear: method.
You may run into issues running code on threads other than the main thread that tries to manipulate the UI.
That sleep(1) is worrisome. You could use a repeating NSTimer instead and eliminate the sleep(1) entirely. Something like:
[NSTimer scheduledTimerWithInterval:2.0 target:self
selector:#selector(root:) userInfo:nil repeats:YES];
For NSTimer, you'd have to change your method, root, to have a signature like
- (void)root:(NSTimer*)theTimer
You need to implement a called viewDidLoad.
- (void) viewDidLoad() {
// your code here
}
I'm sure you have your reasons, but you really shouldn't iteract with UI components in anything other than the UI thread. What you actually need to do is use an NSTimer to call a method on the UI thread multiple times.
What you should be doing is performSelectorOnMainThread when you want to update the UI Thread.
Do your running in the background and update variables that will contain the updated values, then use performSelectorOnMainThread on the View, sending it to a method that will merely update the Textbox with the data in the variables.
You can do anything in a background thread, except update the ui.
Edit: Furthermore I dont recommend using Timers in place of background threads, I have had instances when using Timers, where only so many get created and when I expected a background thread to fire, it never did. The timer actually never fired, even tho it was created.