NSZombie exception on NSEnumerator::nextObject (or, does nextObject touch its query?)

190 views Asked by At

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 dealloced. What I don't get is why the error is happening on this line. If the node I'm getting is what's been dealloced, 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.

2

There are 2 answers

1
Hermann Klecker On BEST ANSWER

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]) {
0
Martin R On

From "Using an Enumerator" in "Collections Programming Topics":

It is not safe to remove, replace, or add to a mutable collection’s elements while enumerating through it. If you need to modify a collection during enumeration, you can either make a copy of the collection and enumerate using the copy or collect the information you require during the enumeration and apply the changes afterwards.

With

[self.contained removeObject:node];

you remove an object from the collection while enumerating it.