Reputation: 131
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
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
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
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