Question

So I'm relative new to objC programming. But not to C. In a more complicated app I think I have a memory leaks. I've programmed this just for make some tests. The app is very simple: it store in a MutableArray a series of integer that rappresent timers scheduled. The app has one NSTimer in the current runloop that check every second if it is the right time to ring comparing a counter with the right element of the MutableArray. Everything works, but memory in debug panel grow up, grow up, grow up… I've try some variants but something still missing for me about ARC. I simply don't understand, since ARC is NOT a garbage collector, why memory grow and what I do wrong. Here is the code:

-(id)initWithLabel:(UILabel *)label {
    self = [super init];
    self.list = [[mtAllarmList alloc]init];
    self.label = label;
    return self;
}

My class init function. I pass a label reference (weak beacause it is own by viewcontroller) to my class. I also allocate and init the class mtAllarmList that contain the MutableArray and other information (in the original app, file to play, volumes, eccetera).

-(void)ClockRun { 
    NSMethodSignature * signature = [mtClockController instanceMethodSignatureForSelector:@selector(check)];
    NSInvocation * selector = [NSInvocation invocationWithMethodSignature: signature];
    [selector setTarget:self];
    [selector setSelector:@selector(check)];

    [[NSRunLoop currentRunLoop] addTimer: self.time = [NSTimer scheduledTimerWithTimeInterval:1
                                                                                   invocation:selector
                                                                                      repeats:YES]
                                 forMode:NSDefaultRunLoopMode];

    [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc]initWithTimeIntervalSinceNow: 30]];
}

ClockRun: is the method the app call to start everything. It simply start the timer that fires every second to check:

-(void)check {
    self.counter++;
    int i = [self.list check:self.counter];
    if(i == 1) {
        [self writeAllarmToLabel:self.label isPlayingAllarmNumber:self.counter];
    }
    else if (i == 2) {
        [self writeAllarmToLabel:self.label theString: @"Stop"];
        [self.time invalidate];
        self.counter = 0;
    }
    else {
        [self writeAllarmToLabel:self.label theString:[[NSString alloc]initWithFormat:@"controllo, %d", self.counter]];
    }
    NSLog(@"controllo %d", self.counter);
}

Check: simply reacts to the return value of [list check: int] methods of mtAllarmList. It returns 1 if timer must ring, 0 if not, and 2 if the sequence ends. In that case self.counter will be set to 0 and the NSTimer will be invalidate.

-(id)init {
    self = [super init];
    self.arrayOfAllarms = [[NSMutableArray alloc]initWithCapacity:0];
    int i;
    for(i=1;i<=30;++i) {
        [self.arrayOfAllarms addObject: [[NSNumber alloc]initWithInt:i*1]];
    }

    for(NSNumber * elemento in self.arrayOfAllarms)
        NSLog(@"ho creato un array con elemento %d", [elemento intValue]);
    return self;
}

In mtAllarmList init method simulates the costruction an array (I've try a variety of patterns) and log all the elements.

-(int)check:(int)second {
    int maxValue = [[self.arrayOfAllarms lastObject] intValue];
    if(maxValue == second){
        self.index = 0;
        return 2;
    } else {
        if ([[self.arrayOfAllarms objectAtIndex:self.index] intValue] == second) {
            self.index++;
            return 1;
        } else {
            return 0;
        }
    }
}

Check methods instead is very elementary and I don't think needs explanations.

So, why this simple very stupid app leaks?

Was it helpful?

Solution

Since you're doing this on the main run loop, you can (and should) simplify the ClockRun method:

- (void)ClockRun {
    self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(check) userInfo:nil repeats:YES];
}

That NSInvocation code was unnecessary and the NSRunLoop code could only introduce problems.

Having said that, this is unlikely to be the source of your memory consumption. And nothing else in the provided code snippets looks like an obvious memory problem. If you're 100% confident that the timer is getting invalidated, then the timer is not the problem. I wonder about the object graph between the view controller at this mtClockController. Or perhaps some circular reference in view controllers (e.g. pushing from A to B and to A again). It's hard to say on the basis of what's been provided thus far.

Sadly, there's not much else we can suggest other than the routine diagnostics. First, I'd run the the app through the static analyzer (by pressing shift+command+B in Xcode, or choosing "Profile" from the Xcode "Product" menu).

Second, you should run your app through Leaks and Allocations tools to identify the what precisely is leaking on each iteration. Do you have extra instances of the view controllers? Or just the mtClockController?

Until you identify what's not being deallocated, it's hard to remedy it. And Instruments is the best tool for identifying what's not getting released. In WWDC 2012 video iOS App Performance: Memory the demonstration sections of the video give pragmatic demonstrations of using Instruments (as well as a wealth of good background info on memory management).

Third, when I've got a situation where I'm not sure if things are getting deallocated when they should, I sometimes include dealloc methods that tell me when the object is deallocated, e.g.:

- (void)dealloc {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

I'd suggest this not only for your key model objects, but your view controller, too. (Sometimes we agonize over our model objects only to realize that it's the view controller, itself, which is be retained by something else.)

Clearly Instruments is a much richer tool, but this can be used to quickly identify failure to deallocate (and show you what's maintaining the strong references).


I ran you app through Instruments, watching your custom objects, and everything is being deallocated properly. Below, I marked generation A, hit the button, let the timer expire, marked generation B, hit the button again, etc. I did that four times, and I then simulated a memory warning, and did one final generation. Everything looks fine (this is a compilation of six screen snapshots in one, showing the total allocations at each of the six generations):

generations

I inspected your Generations, as well as the Allocations themselves, and none of your objects are in there. Everything is getting released fine. The only things there are internal Cocoa objects associated with UIKit and NSString. Cocoa Touch does all sorts of caching of stuff behind the scenes that we have no control over. The reason I did that final "simulator memory warning" was to give Cocoa a chance to purge what it can (and you'll see that despite what Generations reports, the total allocations fell back down a bit).

Bottom line, your code is fine, and there is nothing to worry about here. In the future, don't worry about incidentally stuff showing up in the generations, but rather focus on (a) your classes; and (b) anything sizable. But neither of those apply here.

In fact, if you restrict Instruments to only record information for your classes with the mt prefix (you do this by stopping a recording of Instruments and tap on the "i" button on the Allocations graph and configure the "Recorded Types"), you'll see the sort of graph/generations that you were expecting:

mt classes only

OTHER TIPS

A couple of observations:

  1. Instead of using the invocation form of scheduledTimerWithInterval, try using the selector form directly, in this case it's a lot simpler and clearer to read.

  2. Since you're call runUntilDate directly, I don't think you're getting any autorelease pools created/drained, which would lead to memory leakage, specifically in the check function. Either don't call runUntilDate and allow the normal run loop processing to handle things (the normal preferred mechanism) or wrap check in an @autoreleasepool block.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top