Okay, I've been coding by trade for 12 years, but I'm relatively inexperienced with Obj-C - especially memory management - and I'm getting an error that surprises me.
Here's the code block:
// self.contained is an NSMutableSet
NSEnumerator *e = [self.contained objectEnumerator];
>> while (CCNode *node = [e nextObject]) {
if (!node.body || ![self validate:node]) {
[self.contained removeObject:node];
}
}
I'm getting an _NSZombie_NSException
thrown on the line indicated by >>
. Okay, I get that this means (always?) that I'm accessing an object that's been dealloc
ed. What I don't get is why the error is happening on this line. If the node
I'm getting is what's been dealloc
ed, I'd expect the error on the next line (e.g. when I access node.body
). I can't see how the NSEnumerator
object itself is causing the problem, as it's created immediately before, and if it was the self.contained
set it should have died on the line before, right?
So, does nextObject
actually call some method on the retrieved object (i.e. node
) which would cause the exception to be thrown? That would perhaps explain it, but I wouldn't have thought this would be the case. Or can anyone tell me which object is likely the zombie?
This happens very intermittently, I've had it twice in the last week or so of development, so running the zombie instrument would be unlikely to trap it.
Do NOT add or remove objects from any collection while enumerating it.
Create a copy and enumerate theat copy and manipulate the original. When finished dispose the copy.
Assuming you ARC a small change will do the trick.
for (CCNode *node in [self.contained allObjects]) { // for contained being an NSSet
if (!node.body || ![self validate:node]) {
[self.contained removeObject:node];
}
}
for (CCNode *node in [NSArray arrayWithArray:self.contained]) { // for contained being an NSArray
if (!node.body || ![self validate:node]) {
[self.contained removeObject:node];
}
}
for (CCNode *node in [NSDictionary dictionaryWithDictionary:self.contained]) { // for contained being an NSDictionary. However, allObjects or allKeys may suit you well too depending on what you need.
if (!node.body || ![self validate:node]) {
[self.contained removeObject:node];
}
}
If you don't ARC then add a call to the autorelease method on the newly created unnamed collection object. Like for (CCNode *node in [[self.contained allObjects] autorelease])
If you prefer an explicit enumerator then do:
NSEnumerator *e = [[self.contained allObjects] objectEnumerator];
while (CCNode *node = [e nextObject]) {