Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I've built a simple trivia game using ARC. While profiling its memory usage using the Allocations profiling tool in Xcode, I see that memory is not always freed. For one example of the problem, I have a class for an ActivePlayer object:

ActivePlayer.h:

@interface ActivePlayer : NSObject

@property (nonatomic, strong) NSString * name;
@property (nonatomic) NSInteger overallScore;
@property (nonatomic) NSInteger questionScore;

- (id) initWithName:(NSString *)name;

@end

ActivePlayer.m:

#import "ActivePlayer.h"

@interface ActivePlayer ()

@end


@implementation ActivePlayer

- (id) initWithName:(NSString *)name
{
    self = [self init];
    if (self) {
        self.name = name;
        self.overallScore = 0;
    }
    return self;
}

/*
- (void)dealloc
{
    self.name = nil;
}
*/
@end

And the ActivePlayer is created in a createPlayer method in an ActiveGame class:

[[ActivePlayer alloc] initWithName:name]

I'm executing the following test case: I start a new game (which allocates one ActivePlayer), I answer one question, and then the game ends (and at this point the ActivePlayer is deallocated). I can then start another game and repeat this cycle (each cycle is a "game", as described below). While using the Allocations profiling tool, what I expect to see is that memory has been allocated in the middle of a game but has been deallocated after the game ends (no matter how many times I play a game). But I've found this is not always the case:

BTW: each bulleted row below describes a row in the Objects List tab of the Allocations tool; this site won't let me post a screenshot, hence the text description. All rows are Live; I'm only viewing Created and Still Living allocations.

While game #1 is in progress, I see the following allocations.

  • Category=ActivePlayer; Size=16; Responsible Caller=-[ActiveGame createPlayer:]
  • Category=Malloc 48 Bytes; Size=48; Responsible Caller=-[ActivePlayer initWithName:]

After game #1 is complete, I see the following. The ActivePlayer object has been deallocated, but the 48 bytes is still Live.

  • Category=Malloc 48 Bytes; Size=48; Responsible Caller=-[ActivePlayer initWithName:]

If I start game #2, I see the following while the game is in progress. There are two new allocations in addition to the one from game #1.

  • Category=Malloc 48 Bytes; Size=48; Responsible Caller=-[ActivePlayer initWithName:]
  • Category=ActivePlayer; Size=16; Responsible Caller=-[ActiveGame createPlayer:]
  • Category=Malloc 144 Bytes; Size=144; Responsible Caller=-[ActivePlayer initWithName:]

And after game #2 is complete, I see the following. Again, the ActivePlayer object has been deallocated, but the "Malloc X Bytes" allocations still exist.

  • Category=Malloc 48 Bytes; Size=48; Responsible Caller=-[ActivePlayer initWithName:]
  • Category=Malloc 144 Bytes; Size=144; Responsible Caller=-[ActivePlayer initWithName:]

After that, I get unusual results -- if I play games #3, #4, and #5, I never see in-game rows for Category="Malloc X Bytes", only a new row for Category=ActivePlayer, which is freed up after the game ends. The first two "Malloc" rows, as shown above, continue to persist. I've also seen other odd behavior -- while testing this yesterday using the iPhone 6.0 Simulator, live memory was left behind only after games #2 and #3, but not games #1, #4, and #5. So while memory remains allocated, the times at which it occurs seem to vary across my device and different versions of the simulator.

And my questions:

  • Is my understanding correct that I shouldn't be seeing any live memory from the call to initWithPlayer after the game ends and the ActivePlayer object has been freed?
  • If yes, what's causing it, and how do I deallocate it?
  • Or do I not need to worry about it at all?

Notes:

  • These screenshots come from running my app on an iPhone 4 running iOS 6.1. But I see similar behavior running with the iPhone Simulator for 5.1, 6.0, and 6.1, and I saw it on my iPhone running iOS 6.0 before I upgraded.
  • In ActivePlayer.m, the dealloc method is currently commented out, though I've tested while it's been uncommented and have verified that it's being called (by the system; I don't directly call dealloc anywhere). Either way, the behavior is the same.
  • For what it's worth, nothing is reported by the Leaks profiling tool.
  • While this is one example that results in 192 bytes of live memory that I believe should be freed, I'm seeing this with many of my classes, i.e. it appears that memory allocation grows over time, which I would think is a problem.
share|improve this question
1  
The problem is likely elsewhere in your code. Can you post more? Something must be holding a reference to your ActivePlayer objects, some of the time. Can you see where, and by which objects, the retain count is incremented? –  noa Feb 12 at 23:58
 
I thought of that as well and have looked for other places where the ActivePlayer object might be referenced. But if it were the case that some other object were holding a reference to the ActivePlayer, then I would have expected that the row "Category=ActivePlayer; Size=16; Responsible Caller=+[ActivePlayer initWithName:]" would still exist in the Objects List (it does not) and (2) the object's dealloc method would not be called (which I've verified that it is being called). Again, if my understanding is correct... –  Mike W. Feb 13 at 0:09
 
Let's use that dealloc to your debugging advantage: create a static int in the file and increment it right next to your alloc init line. Log it there, too. Then decrement and log it inside dealloc. –  danh Feb 13 at 0:34
 
@danh OK, I just tried that. As I expected, the static int is incremented to 1 in initWithName when a game is started and decremented to 0 in dealloc when a game completes. Of course, this won't catch the case where the ActivePlayer object is referenced somewhere I didn't expect, but I still believe that dealloc wouldn't have been called at all if this were true. –  Mike W. Feb 13 at 0:53
 
Doesn't that test tell you that you're in good shape? You allocated one like you expected and it got deallocated like you expected. I think that means it isn't leaking. Or am I misunderstanding you? (If it was referenced somewhere you didn't expect and therefore retained, you wouldn't have seen the dealloc). –  danh Feb 13 at 1:01
show 2 more comments

3 Answers

Your code listed is fine. It looks like you're still maintaining a reference to the original ActivePlayer, somewhere else in your code.

As a side note, your pattern for creating an ActivePlayer isn't the norm - generally a class doesn't call alloc from within an init method. Instead, the caller should perform:

[[ActivePlayer alloc] initWithName:@"Bob"];

and your init method should work with the return value of

[super init];
share|improve this answer
 
Yes, you're right. That's a rookie mistake on my part (writing the init method that way). I've just changed it and, at first blush, it seems the extra "Malloc X Bytes" rows I was seeing have disappeared as well. Gotta do some other code mods and testing to see if my incorrectly-implemented init method was inadvertently causing the memory problem. –  Mike W. Feb 13 at 1:25
add comment

I find it very strange that your constructor is static (+ sign). The naming convention mandates that methods with names prefixed as init would return managed memory objects. I suspect that internally method's results being evaluated with respect to their method names.

share|improve this answer
 
Yes, you and @danh are of course correct. See my comment above. This may have solved the problem, but won't know without a few more code changes and a little more testing. –  Mike W. Feb 13 at 1:28
1  
On second reading, I highly suspect this is the correct problem. The names of methods mattered before ARC – methods which started with -init should give the caller a +1 retain count object; +activePlayerWithName would be autoreleased with an effective 0 retain count. Perhaps ARC somehow carries some of this logic over and behaves incorrectly with an +init method. –  noa Feb 13 at 4:21
add comment

I think the instance counting test determined that your code is not leaking ActivePlayers. As an aside, a better form on the constructor and inits are like this:

// .h

@interface ActivePlayer : NSObject

+ (id)activePlayerWithName:(NSString *)name;

@end

// .m

+ (id)activePlayerWithName:(NSString *)name {
    return [[self alloc] initWithName:name];
}

// if you want to make this public (include in .h interface) you can
// the callers will have the choice of alloc init pattern, or the factory
//
- (id)initWithName:(NSString *)name {
    self = [self init];
    if (self) {
        _name = name;
    }
    return self;
}

Then the caller does this:

ActivePlayer *activePlayer = [ActivePlayer activePlayerWithName:@"Charlie"];
share|improve this answer
 
I made the code changes as recommended here, did further testing, and the problem persists. As a newbie to this site, would it now be better to update the original question to include the changed code + changes to the specifics of what I'm seeing in the Allocations tool, or to post a comment to one of the answers or the original question? Thanks in advance for any guidance. –  Mike W. Feb 13 at 19:50
 
I'd handle it this way: if the question is still about leaking an ActivePlayer object, stick with this one, and post some evidence that it's leaking (e.g. leak tool screen shots, nslog output, etc.) If you think something else is wrong (including leaking some other object) then start a new question. New question will get that first rush of activity, but will get down-voted if somebody perceives that you're still asking this one. –  danh Feb 13 at 21:14
 
My take on this one is that there's not strong evidence it's leaking. The simulator code leaks in some places, and the sdk might leak too. I also wouldn't worry too much about it unless you're leaking large volume in a busy code path. –  danh Feb 13 at 21:16
 
It's still leaking as described, but the details are slightly different since I've changed some code. I think I should use this question, but what I was asking is -- should I update the text of this question with the new details, or should I leave the original text as is but provide updated information in a comment to the original question? –  Mike W. Feb 13 at 21:19
 
Ah, just read your second update. Maybe I should ignore for now, but it seems like there are a whole lot of extraneous "Malloc X Bytes" objects scattered throughout. –  Mike W. Feb 13 at 21:22
show 1 more comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.