Vibha R Kedilaya Vibha R Kedilaya - 4 months ago 24
Objective-C Question

A label is getting deallocated due to memory leak ,what shall i do?

code :Thus this code worked fine for when timer id stopped and restarted for few times ,But crashes when we repeat the same for more than 30 times.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
aTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(ShowTime:) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] run];});


updating label

-(void)ShowTime:(NSTimer *) timer{

NSTimeInterval now = [[NSDate date] timeIntervalSince1970];

double Timeinterval= (now - StartClickTime)/60 ;

double wait=([FrequencyPerHour doubleValue]+[PromptTime doubleValue]+2.75);
double warningTime=([FrequencyPerHour doubleValue]+[WarningTime doubleValue]);
if (WarninigBeep == NO) {


if ((Timeinterval*60) >= warningTime)
{
// NSString *path = [NSString stringWithFormat:@"%@/rapidbeep9.mp3", [[NSBundle mainBundle] resourcePath]];
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"rapidbeep9"
ofType:@"mp3"
inDirectory:nil];

NSURL *soundUrl = [NSURL fileURLWithPath:filePath];
// Create audio player object and initialize with URL to sound
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
[_audioPlayer play];
WarninigBeep=YES;


}
}
[lbltimerDisplay setText:[NSString stringWithFormat:@"%.4f", Timeinterval]];}

Rob Rob
Answer

There are a couple of problems here:

  • You're running the timer on a background thread, but you're updating the UI, which should always take place on the main thread. If you're going to run this on a background thread, make sure to dispatch the UI update back to the main thread:

    dispatch_async(dispatch_get_main_queue(), ^{
        self.timerLabel.text = [NSString stringWithFormat:@"%.4f", Timeinterval];
    });
    
  • You're starting a run loop on this background thread, but never stop it. If you look at the documentation for run, it warns you:

    If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:

    BOOL shouldKeepRunning = YES;        // global
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    

    If you don't stop the run loop and repeat this process too many times, you'll consume all of the very limited global queue worker threads, slow down the app with all of these abandoned run loops, and use up your memory.

  • Even easier, if you want a timer to run on a background thread, don't use NSTimer/NSRunLoop at all, but rather instead use a GCD timer which can run on a background queue without needing a run loop. So, declare a property for the timer:

    @property (nonatomic, strong) dispatch_source_t timer;
    

    and then start and stop the timer like so:

    - (void)startTimer {
        dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer.16782529", 0);
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        if (self.timer) {
            dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 0.1 * NSEC_PER_SEC, 0);
    
            dispatch_source_set_event_handler(self.timer, ^{
                // do/call whatever you want here
            });
    
            dispatch_resume(self.timer);
        }
    }
    
    - (void)cancelTimer {
        dispatch_source_cancel(self.timer);
    }
    
  • I don't think you should tackle this right now, but if I wanted to have a constantly updating display of time elapsed and then play a sound once per second, I would:

    • Use a display link to display the elapsed time. It's like a timer, but optimized for screen updates. If you're updating something on screen, this delivers the best UX.

    • To play the sound (once per second?), I'd just use a standard timer for that. And I wouldn't be inclined to create a new AVAudioPlayer each time, but do that once and just play the audio with whatever frequency makes sense.

Having said all of this, I have a vague suspicion that there might be other, deeper problems here, too, but it seems that you should first fix these timer/runloop problems, and then see if other problems persist.

The title of this question presumes that something was deallocated due to memory pressure. That's not how memory pressure would manifest itself. The more likely problem is that the view controller has been deallocated while this background process was still firing. I'd suggest adding a NSLog statement in the deallocated routine and see if the crash (if it still occurs at all having fixed the above issues) occurs before or after the deallocation.

Regardless, if it's still crashing, look at the stack trace (or add an exception breakpoint) so you can find where precisely it's crashing. It's hard to diagnose without knowing where precisely the problem rests.