I have a problem with an Objective-C object (in an iOS game app) that is being mysteriously deallocated.
The object is a GameCharacter instance which is instantiated like so:
for (int c = 0; c < kNrOfGuards; c++) {
GameCharacter* guard = [[GameCharacter alloc] initGuard:self sprite:guardSprite];
[characterArray addObject:guard];
[guard release];
}
I also have a convenience method for finding a GameCharacter:
- (GameCharacter*)findCharacterWithIndex:(int)index {
return [characterArray objectAtIndex:index];
}
And the code that generates the error looks like:
for (int c = 0; c < [self characterCount]; c++) {
GameCharacter* tempCharacter = [self findCharacterWithIndex:c];
if (tempCharacter.playerId == playerIndex]) {
...
}
}
Running this code for some time (never immediately) generates an error in the Console:
[GameCharacter playerId]: message sent to deallocated instance 0x4e47560
With the NSZombieEnabled trick I've managed to track down the object(s) that is causing the problem, but I still can't understand why this object is being deallocated. Searching my code for "release"/"dealloc" does not produce any clues.
I've tried removing the "release" (and even adding a "retain"!) to the alloc/init loop (see top), it seems to extend the time the app can run but not remove the problem entirely.
Any hints would be much appreciated!
EDIT
Thanks to quixoto, Olie, Eiko, tc., I've figured out that it is my GameCharacter object that is being deallocated, but I still don't understand quite why. Here is the trace log in reverse chronological order:
#0 -[GameCharacter dealloc]
#1 objc_setProperty
#2 -[TiledGroundLayer setSelectedCharacter:]
#3 -[TiledGroundLayer selectNextCharacterForPlayer:searchStep:]
#4 -[GameScene selectNextCharacter:]
#5 -[GameScene endTurn]
#6 -[HUDLayer onClickDone:]
What happens here, is that the user clicks "Done", the selected character on screen is changed and thus the property selectedCharacter
on TiledGroundLayer (step #2-4). Since selectedCharacter
owns the previous GameCharacter object, it seems it is being deallocated. But why is it not being retained properly by the NSMutableArray ([characterArray addObject:guard];
)?
In Objective-C, any character , numeric or boolean literal prefixed with the '@' character will evaluate to a pointer to an NSNumber object (In this case), initialized with that value. C's type suffixes may be used to control the size of numeric literals. '@' is used a lot in the objective-C world.
Objective-C is slower than C/C++. The reason being the runtime of Objective-C which dispatches methods lookups dynamically at runtime the same way as Smalltalk, from which it has taken over this execution model.
The DEALLOC operation frees one previous allocation of heap storage. pointer-name is a pointer that must be the value previously set by a heap-storage allocation operation (either an ALLOC operation in RPG, or some other heap-storage allocation mechanism).
There's not quite enough code here to tell what the problem is but, from the error message, I'd guess that the playerId object is what's not being retained. That is, it seems that your tempCharacter is fine, but not the playerId field.
If you have
@property(nonatomic,retain) SomeObject *playerId;
then remember that
playerId = foo;
will NOT hold onto the object for you. You must use the accessor:
self.playerId = foo;
EDIT in response to Tom's question-edit:
I absolutely, positively guarantee you that objects placed in an NSMutableArray are retained by that array until (a) they are removed or (b) the array is released. So you can stop looking there, the problem is somewhere else. :)
One thing you can try is to add the following code to the object that is being released when you think it shouldn't:
#pragma mark -
#pragma mark Memory-use debugging
#define DEBUG_RETAIN_RELEASE 0
#define DEBUG_ALLOC_DEALLOC 0
#if DEBUG_ALLOC_DEALLOC
static int allocCounter = 0;
+(id)alloc
{
id me = [super alloc];
NSLog(@"%@ ALLOC (%2d): %@", [me class], ++allocCounter, me);
return me;
}
#endif
#if DEBUG_RETAIN_RELEASE
- (id)retain
{
id result = [super retain];
NSLog(@"%@ retain %@, count: %2d", [self class], self, [self retainCount]);
return result;
}
- (void)release
{
// we have to log BEFORE the release, in case it's the last one! e
NSLog(@"%@ RELEASE %@, count: %2d", [self class], self, ([self retainCount] - 1));
[super release];
}
- (id)autorelease
{
id result = [super autorelease];
NSLog(@"%@ AUTOrelease %@, count: %2d", [self class], self, [self retainCount]);
return result;
}
#endif
// ...
- (void)dealloc
{
#if DEBUG_ALLOC_DEALLOC
NSLog(@"%@ dealloc (%2d): %@", [self class], --allocCounter, self);
#endif
[self releaseMyStuff];
[super dealloc];
}
Then start with DEBUG_ALLOC_DEALLOC = 1 and put a breakpoint on the dealloc log statement. If that doesn't help, set DEBUG_RETAIN_RELEASE = 1 and break on both retain & release.
You'll be surprised at all the iOS retains you get, but don't worry about it, iOS promises balanced retain-release, if used properly. I'm just warning you because you may be expecting a much lower retain count, and be surprised to see it climb during some operation or another.
Luck!
Based on your update:
#0 -[GameCharacter dealloc]
#1 objc_setProperty
#2 -[TiledGroundLayer setSelectedCharacter:]
I would guess that you're releasing your existing reference to an object in your setter, followed by retaining the new copy. However, if the new object happens to be the exact same object as the existing reference, you might be sending the retain
message to an already deallocated object.
-(void) setSelectedCharacter: (GameCharacter*) newCharacter
{
[character release]; // Oops if character == newCharacter
character = [newCharacter retain];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With