Mike W.
Mike W.

Reputation: 131

memory not being released in iOS app using ARC

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.

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

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.

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.

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:

Notes:

Upvotes: 3

Views: 1783

Answers (3)

bioffe
bioffe

Reputation: 6383

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.

Upvotes: 2

danh
danh

Reputation: 62676

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"];

Upvotes: 1

Chris McGrath
Chris McGrath

Reputation: 1936

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];

Upvotes: 3

Related Questions