iOS - NSTimer - SIGABRT after invalidate - Edit Now DeAlloc Msg - iphone

I have my NSTimer embedded in a class that plays image sequences. Basically it loops and changes a UIImageView. Everything goes well if I let the image sequence finish but... on it's own if I try to stop the timer while it is playing I get a sigabrt. Edit: No longer a sigabrt but now a DeAlloc I can't explain.
The "stop" at the end of a frame sequence is the same stop I am calling mid sequence.
So what might cause an NSTimer to break mid function and DeAlloc. More to the point what might I look at to fix it.
Thanks.
I am using some example code from here: http://www.modejong.com/iOS/PNGAnimatorDemo.zip
Edit: I'll add what I believe to be the pertinent code here.
// Invoke this method to start the animation
- (void) startAnimating
{
self.animationTimer = [NSTimer timerWithTimeInterval: animationFrameDuration target: self selector: #selector(animationTimerCallback:) userInfo: NULL repeats: TRUE];
[[NSRunLoop currentRunLoop] addTimer: animationTimer forMode: NSDefaultRunLoopMode];
animationStep = 0;
if (avAudioPlayer != nil)
[avAudioPlayer play];
// Send notification to object(s) that regestered interest in a start action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStartNotification
object:self];
}
- (void) animationTimerCallback: (NSTimer *)timer {
if (![self isAnimating])
return;
NSTimeInterval currentTime;
NSUInteger frameNow;
if (avAudioPlayer == nil) {
self.animationStep += 1;
// currentTime = animationStep * animationFrameDuration;
frameNow = animationStep;
} else {
currentTime = avAudioPlayer.currentTime;
frameNow = (NSInteger) (currentTime / animationFrameDuration);
}
// Limit the range of frameNow to [0, SIZE-1]
if (frameNow < 0) {
frameNow = 0;
} else if (frameNow >= animationNumFrames) {
frameNow = animationNumFrames - 1;
}
[self animationShowFrame: frameNow];
// animationStep = frameNow + 1;
if (animationStep >= animationNumFrames) {
[self stopAnimating];
// Continue to loop animation until loop counter reaches 0
if (animationRepeatCount > 0) {
self.animationRepeatCount = animationRepeatCount - 1;
[self startAnimating];
}
}
}
- (void) stopAnimating
{
if (![self isAnimating])
return;
[animationTimer invalidate];
self.animationTimer = nil;
animationStep = animationNumFrames - 1;
[self animationShowFrame: animationStep];
if (avAudioPlayer != nil) {
[avAudioPlayer stop];
avAudioPlayer.currentTime = 0.0;
self->lastReportedTime = 0.0;
}
// Send notification to object(s) that regestered interest in a stop action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStopNotification
object:self];
}
Edit2: So I commented out an NSAssert in DeAlloc, commenting that out shed a bit more light. Now getting to self.animationTimer = nil; and saying *** -ImageAnimator setAnimationTimer:]: Message sent to deallocated instance.
DeAlloc is being called right when I invalidate the timer... so I'm a bit confused here.

I have a solution which is working but not optimal.
I just added another function to set the frame it's on to the last frame and it ends the sequence as I need.
But it doesn't solve the question of why it's doing it's crashing if I try to stop the sequence mid run.

Well, I wrote this code so I can assure you that it should be working just fine :) You need to invoke stopAnimating before you are done with the view, the only way you would have got the assert in dealloc is if the view was deallocated while it was still animating. That should not happen, which is exactly why there way an assert there. There is also a big comment explaining the assert, what is unclear about that?
- (void)dealloc {
// This object can't be deallocated while animating, this could
// only happen if user code incorrectly dropped the last ref.
NSAssert([self isAnimating] == FALSE, #"dealloc while still animating");
self.animationURLs = nil;
self.imageView = nil;
self.animationData = nil;
self.animationTimer = nil;
[super dealloc];
}
Just call:
[view stopAnimating];
Before you remove the view from its superview and everything will be just fine.

Related

Reset NSTimer after navigating to another view

Currently I am developing a game which needs a stop watch say 1 minute,when user taps the play button it enters the game screen,I want the to run the stop watch count down of 1 minute 01:00,00:59,00:58 etc. For that reason I searched and found NSTimer would be the ideal choice to implement a stop watch.Hence I took a label,created an instance of NSTimer,assigned time interval and started decrementing the value in timer label,i.e.:
-(void)viewDidAppear:(BOOL)animated
{
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(viewWillAppear:) userInfo:nil repeats:YES];
[super viewDidAppear:YES];
}
-(void)viewWillAppear:(BOOL)animated
{
static int currentTime = 60;
int newTime = currentTime--;
int minutesRemaining = newTime / 60; // integer division, truncates fractional part
int secondsRemaining = newTime % 60; // modulo division
self.timerLabel.text = [NSString stringWithFormat:#"%02d:%02d", minutesRemaining, secondsRemaining];
if ([self.timerLabel.text isEqualToString:#"00:00"])
{
[stopWatchTimer invalidate];
}
[super viewWillAppear:YES];
}
The problem here is the timer starts running 01:00,00:59,00:58 and goes on,say at 53rd second,I navigated to another view and came back,it is running from 00:53,00:52 and so on,but I want it to run from 01:00 and for that I implemented invalidating NSTimer in viewDidDisappear i.e.
-(void)viewDidDisappear:(BOOL)animated
{
if ([stopWatchTimer isValid])
{
[stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
[super viewDidDisappear:YES];
}
Still the same issue exists!
Done lots of Research on the issue and found no answer useful and working.
Can some one please guide me,any help is appreciated.
Thanks every one in advance :)
You are use the selector viewWillAppear for the timer. viewWillAppear is a method on a viewController that gets called when the view appears on screen, you should not call it yourself. Instead, create your own method that decrements the timer:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
currentTime = 60; // This could be an instance variable
self.timerLabel.text = #"01:00";
}
-(void)updateTime {
int newTime = currentTime--;
int minutesRemaining = newTime / 60; // integer division, truncates fractional part
int secondsRemaining = newTime % 60; // modulo division
self.timerLabel.text = [NSString stringWithFormat:#"%02d:%02d", minutesRemaining, secondsRemaining];
if ([self.timerLabel.text isEqualToString:#"00:00"])
{
[self.stopWatchTimer invalidate];
}
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if ([self.stopWatchTimer isValid])
{
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
}
With this technique the timer is responsible for calling every second, but you will probably have timing issues after some seconds. To have a more accurate timing, you should store the time that you started the countdown and then on every updateTime call, compare the current time with the stored started time.
Add to your interface:
#property (nonatomic) NSTimeInterval startTime;
then in the implementation:
-(void)viewDidAppear:(BOOL)animated
{
self.startTime = [NSDate timeIntervalSinceReferenceDate];
self.duration = 60; // How long the countdown should be
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(updateTime)
userInfo:nil
repeats:YES];
self.timerLabel.text = #"01:00"; // Make sure this represents the countdown time
}
-(void)updateTime {
int newTime = self.duration - (round([NSDate timeIntervalSinceReferenceDate] - self.startTime));
int minutesRemaining = newTime / 60; // integer division, truncates fractional part
int secondsRemaining = newTime % 60; // modulo division
self.timerLabel.text = [NSString stringWithFormat:#"%02d:%02d", minutesRemaining, secondsRemaining];
if (newTime < 1)
{
[self.stopWatchTimer invalidate];
/* Do more stuff here */
}
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if ([self.stopWatchTimer isValid])
{
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
}
Some extra (unrelated) comments on the code:
when implementing viewDidDisappear: pass the animated parameter to the call to super:
[super viewDidDisappear:animated];
when implementing viewDidAppear: pass the animated parameter to the call to super, also, make sure you call super first this in the method, before doing anything else

Create number of dynamic NSTimers not repeating on very first load, fine after that

I am creating a number (~5) NSTimer instances from data stored in a SQLite database using the following code below:
-(void)setupNotificationTimer:(NSDictionary *)timerDetails{
NSLog(#"TIMER REPEATS VALUE: %#", [timerDetails objectForKey:#"repeats"]);
NSLog(#"INTERVAL: %f", [[timerDetails objectForKey:#"interval"] floatValue]);
bool repeat = [[timerDetails objectForKey:#"repeats"] boolValue];
if (repeat) {
NSLog(#"Should Repeat");
}
else{
NSLog(#"Should Not Repeat");
}
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:[[timerDetails objectForKey:#"interval"] floatValue]
target:self
selector:#selector(fireTimer:)
userInfo:timerDetails
repeats:repeat];
[timer fire];
[timers addObject:timer]; //'timers' is a property of the AppDelegate
}
-(void)fireTimer:(NSTimer *)timer {
NSLog(#"Timer Fired");
NSMutableDictionary *dict = [timer userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:[dict objectForKey:#"notificationName"] object:nil];
}
When I setup these timers the very first time the app loads the timer is setup correctly as the NSLog output below shows but does not repeat despite clearly being set to repeat. Each timer fires once then does not continue.
2013-04-03 11:12:53.541 Test[2635:752b] TIMER REPEATS VALUE: 1
2013-04-03 11:12:53.542 Test[2635:752b] INTERVAL: 10.000000
2013-04-03 11:12:53.543 Test[2635:752b] Should Repeat
2013-04-03 11:12:53.544 Test[2635:752b] Timer Fired
The strange thing happens when I close the app and then re-open it, the timers are created and actually fire repeatedly. This app is also a universal iPhone and iPad app and I only seem to get this behaviour when running the app on iPhone. I really am stumped by this and any help is appreciated.
Edit
As requested the code to cancel the timers:
-(void)viewWillDisappear:(BOOL)animated{
NSArray *viewControllersSet = self.navigationController.viewControllers;
if (viewControllersSet.count > 1 && [viewControllersSet objectAtIndex:viewControllersSet.count-2] == self) {
// View is disappearing because a new view controller was pushed onto the stack
NSLog(#"New view controller was pushed");
} else if ([viewControllersSet indexOfObject:self] == NSNotFound) {
// View is disappearing because it was popped from the stack
NSLog(#"View controller was popped");
for(int i = 0; i < [[appDelegate timers] count]; i ++){
NSTimer *timer = [[appDelegate timers] objectAtIndex:i];
[timer invalidate];
timer = nil;
}
[[appDelegate timers] removeAllObjects];
}
}
If an NSTimer does not fire after being created with scheduledTimerWithTimeInterval, it usually means that it has not been attached to the correct runloop. Could it be that your method setupNotificationTimer() is called with different runloops during initialization and when returning to the foreground? (i.e. in the context of a different thread or similar)
This code would work around the problem:
NSTimer* timer = [NSTimer timerWithTimeInterval:[[timerDetails objectForKey:#"interval"] floatValue]
target:self
selector:#selector(fireTimer:)
userInfo:timerDetails
repeats:repeat];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
This ensures that your timers are scheduled on the main runloop and will fire.

AVCaptureSession - Stop Running - take a long long time

I use ZXing for an app, this is mainly the same code than the ZXing original code except that I allow to scan several time in a row (ie., the ZXingWidgetController is not necesseraly dismissed as soon as something is detected).
I experience a long long freeze (sometimes it never ends) when I press the dismiss button that call
- (void)cancelled {
// if (!self.isStatusBarHidden) {
// [[UIApplication sharedApplication] setStatusBarHidden:NO];
// }
[self stopCapture];
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}
with
- (void)stopCapture {
decoding = NO;
#if HAS_AVFF
if([captureSession isRunning])[captureSession stopRunning];
AVCaptureInput* input = [captureSession.inputs objectAtIndex:0];
[captureSession removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[captureSession.outputs objectAtIndex:0];
[captureSession removeOutput:output];
[self.prevLayer removeFromSuperlayer];
/*
// heebee jeebees here ... is iOS still writing into the layer?
if (self.prevLayer) {
layer.session = nil;
AVCaptureVideoPreviewLayer* layer = prevLayer;
[self.prevLayer retain];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12000000000), dispatch_get_main_queue(), ^{
[layer release];
});
}
*/
self.prevLayer = nil;
self.captureSession = nil;
#endif
}
(please notice that the dismissModalViewController that remove the view is within the delegate method)
I experience the freeze only while dismissing only if I made several scans in a row, and only with an iPhone 4 (no freeze with a 4S)
Any idea ?
Cheers
Rom
According to the AV Cam View Controller Example calling startRunning or stopRunning does not return until the session completes the requested operation. Since you are sending these messages to the session on the main thread, it freezes all the UI until the requested operation completes. What I would recommend is that you wrap your calls in an Asynchronous dispatch so that the view does not lock-up.
- (void)cancelled
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self stopCapture];
});
//You might want to think about putting the following in another method
//and calling it when the stop capture method finishes
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}

how to implement a reverse countdown and then call a method

I am developing a 2d game in which on my game screen I have to implement a reverse timer from 30 sec till 0 sec and if a player does not move his character he will win otherwise he will lose and the game will be over.
This is my init method:
-(id)init
{
if(self==[super init])
{
self.isTouchEnabled=YES;
self.isAccelerometerEnabled=YES;
CGSize size=[[CCDirector sharedDirector] winSize];
screenwidth=size.width;
screenheight=size.height;
west_pic=[CCSprite spriteWithFile:#"west_pic.png"];
west_pic.anchorPoint=ccp(1, 1);
west_pic.scaleX=1.4;
west_pic.position=ccp(size.width, size.height);
[self addChild:west_pic];
label1=[CCLabelTTF labelWithString:#"Level One" fontName:#"Arial" fontSize:20];
label1.position=ccp(size.width/3.8,size.height/1.2);
[self addChild:label1];
label2=[CCLabelTTF labelWithString:#"Lives :" fontName:#"ArcadeClassic" fontSize:18];
label2.position=ccp(size.width/1.5,size.height/8.2);
[self addChild:label2];
player=[CCSprite spriteWithFile:#"player.png"];
player.position=ccp(size.width/1.7, size.height/2);
player.scale=0.2;
player.tag=2;
player.anchorPoint=ccp(1, 0);
[self addChild:player];
[self schedule:#selector(updateplayer:) interval:1.0f/60.0f];
}
return self;
}
for example, you can have an instance with number of seconds, that player must not to move character, then you must have ssome event methods to know when the character begins and stops movement, create updatable label(if you want to show the rest of the time to player) and schedule timer, which will decrease number of seconds. smth like this
- (void) onEnter
{
[super onEnter];
// if player character not move on start
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onExit
{
[self unscheduleAllSelectors];
[super onExit];
}
- (void) onCharacterStop
{
m_restTime = // your time left. in your case, 30 sec
// if you have time label
[self updateRestTimeLabel];
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onCharacterBeginMovement
{
[self unscheduleSelector:#selector(reduceRestTime)];
}
- (void) reduceRestTime
{
m_restTime--;
// if you have time label
[self updateRestTimeLabel];
if( m_timeLeft == 0 )
{
[self unscheduleSelector:#selector(reduceRestTime)];
// your win code
}
}
- (void) updateRestTimeLabel
{
[m_timeLabel setString:[NSString stringWithFormat:#"Time left: %d", m_timeLeft]];
}

How to define a centralized timer for app in iphone?

I need to perform certain tasks (many tasks) at different intervals. How can i implement it with single NStimer instance as individual timers will create overhead?
I am doing this exact thing.
in my appdelegate i have a method for the timer and threads (i've removed the threads for simplicity):
-(void)startupThreadsAndTimers{
//updatetime
timer = [NSTimer scheduledTimerWithTimeInterval:(1)
target:self
selector:#selector(updateTime)
userInfo:nil
repeats:YES];
}
and i have a method that the timer triggers
-(void)updateTime{
[[NSNotificationCenter defaultCenter] postNotificationName: #"PULSE" object: nil];
}
in the "awakefromnib" method of the appdelgate class i have this:
[self startupThreads];
Now in the veiwdidload method on the viewcontrollers that need to subscribe to this (to update a clock) i subscribe to the notification centre:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateTimeLeft) name:#"PULSE" object:nil];
And i have a method called "updateTimeLeft"
This works really well but i will revisit it to use threads for a bit of performance at a later time.
To have things trigger at different intervals you could change these selectors to parse a time value and have the subscribing view controllers use a modulus to decide what to do.
i.e.
switch (timerValue % 15){
case 1:
[self someMethod];
break;
case 2:
[self someOtherMethod];
break;
}
Hope this helps :)
You can use NSNotificationCenter to define a notification and at particular time it dispatches a notification so other class can be notified and do some task accordingly...
refer this tutorial...
I had to use this in my iPhone App, this is what I did:
#define kUpdateInterval(x) (iPerformanceTime % x == 0)
int iPerformanceTime;
-(void) mPerformanceGameUpdates {
if (iPerformanceTime == 0) { //Initialization
}
if (kUpdateInterval(1)) { //Every 1sec
}
if (kUpdateInterval(2)) { //Every 2sec
}
if (kUpdateInterval(5)) { //Every 5sec
}
if (kUpdateInterval(10)) { //Every 10sec
}
if (kUpdateInterval(15)) { //Every 15sec
}
if (kUpdateInterval(20)) { //Every 20sec
}
if (kUpdateInterval(25)) { //Every 25sec
}
iPerformanceTime ++;
}
//The method below helps you out a lot because it makes sure that your timer
//doesn't start again when it already has, making it screw up your game intervals
-(void) mPerformanceTmrGameUpdatesLoopShouldRun:(BOOL)bGameUpdatesShouldRun {
if (bGameUpdatesShouldRun == TRUE) {
if (bPerformanceGameUpdatesRunning == FALSE) {
tmrPerformanceGameUpdates = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(mPerformanceGameUpdates) userInfo:nil repeats:YES];
bPerformanceGameUpdatesRunning = TRUE;
}
} else if (bGameUpdatesShouldRun == FALSE) {
if (bPerformanceGameUpdatesRunning == TRUE) {
[tmrPerformanceGameUpdates invalidate];
bPerformanceGameUpdatesRunning = FALSE;
}
}
}
-(void) viewDidLoad {
[self mPerformanceTmrGameUpdatesLoopShouldRun:TRUE];
//TRUE makes it run, FALSE makes it stop
}
Just change the update intervals and you'll be good to go!
Define in app delegate and access it elsewhere using the UIApplication sharedApplication.
Hope this helps