In my iphone game, when run on the ios 4.0 simulator everything works fine. However when running with the 5.0 simulator or on a 5.0+ device, the app crashes on the first level leaving an error:
Terminating app due to uncaught exception 'NSGenericException', reason:
*** Collection <__NSArrayM: 0x1416eea0> was mutated while being enumerated.
*** First throw call stack:
(0x1d6a052 0x20d0d0a 0x1d69c21 0x6f8e 0x8bd48 0x94020 0xba169 0xbcee4 0x85a2db 0x85a1af 0x1d3e966 0x1d3e407 0x1ca17c0 0x1ca0db4 0x1ca0ccb 0x2702879 0x270293e 0x90fa9b 0x1f31 0x1eb5 0x1)
terminate called throwing an exception(lldb)
I've think i've narrowed the problem down to this piece of code. I understand that the problem is removing objects within the for-loop but cannot seem to figure out a solution.
Here is my code:
//remove the projectile
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
//remove the projectile
for (CCSprite *targetDel in targetsToDelete) {
targetDel.position = ccp(-2000, -2000);
[self removeChild:targetDel cleanup:YES];
[_targets removeObject:targetDel];
}
[targetsToDelete release];
Please help, been trying to figure out for the past few days.
In short, the expression:
for (OBJ * VAR in COLLECTION) {
uses a technique called Fast Enumeration. What happens here is the compiler inserts some hidden backing storage on the stack and requests a collection of elements from a type which may be enumerated. Because it grabs many objects at once and iterates over them, it is an error to mutate the container (COLLECTION) while it is being enumerated because the stack area and the collection can fall out of sync.
The workaround is to avoid fast enumeration when you mutate what you are enumerating -- either that, or you could enumerate a copy in some cases. The standard for(i;c;e) loop does not use fast iteration -- but the for(in) does use it.
One way to alter the program to avoid fast enumeration errors is:
// remove the projectile
while (projectilesToDelete.count) {
CCSprite * projectile = projectilesToDelete[0];
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
// remove the projectile
while (targetsToDelete.count) {
CCSprite * targetDel = targetsToDelete[0];
targetDel.position = ccp(-2000, -2000);
[self removeChild:targetDel cleanup:YES];
[_targets removeObject:targetDel];
}
[targetsToDelete release];
Problem with your code is that, consider this..... projectilesToDelete array has 3 elements(A, B, C)...
In first iteration, you removeObject(A).. at that array count becomes 2 i.e., array content will be (B, C) but it considers it to be 3(B,C, Nil).
So Array got modified while enumerating. So it will give an exception.
change the code to this
// get array length
int count = projectilesToDelete. length;
// iterate through the array
for (int iter = 0; iter < count; inter++)
{
CCSprite *projectile = projectilesToDelete [iter];
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
// decrement count
count--;
iter--;
}
Related
I have a flood fill function:
-(void) fillArea :(int) fillNum x:(int) xSpot y:(int) ySpot
{
int gridValue = 1;
int gridCount = [theGrid count];
[[theGrid objectAtIndex:(xSpot+ySpot*120)] getValue:&gridValue];
if (gridValue != 0) {
return;
}
[theGrid replaceObjectAtIndex:(xSpot + ySpot*120) withObject:[NSNumber numberWithInt:fillNum]];
[self fillArea:fillNum x:(xSpot+1) y:(ySpot)];
[self fillArea:fillNum x:(xSpot+1) y:(ySpot-1)];
[self fillArea:fillNum x:(xSpot) y:(ySpot-1)];
[self fillArea:fillNum x:(xSpot-1) y:(ySpot-1)];
[self fillArea:fillNum x:(xSpot-1) y:(ySpot)];
[self fillArea:fillNum x:(xSpot-1) y:(ySpot+1)];
[self fillArea:fillNum x:(xSpot) y:(ySpot+1)];
[self fillArea:fillNum x:(xSpot+1) y:(ySpot+1)];
return;
}
theGrid is an NSMutableArray of ints (either a 0 or a 1). It is just a 1D array that simulates a 2D array by multiplying the ySpot by 120 (the width of my grid). I checked the gridCount and it is equal to 9600.
However, I get an exc_bad_access at [[theGrid objectAtIndex:(xSpot+ySpot*120)] getValue:&gridValue]. I check my xSpot and ySpot when this happens and I know that (xSpot+ySpot*120) < 9600 every time. So I know it's not that I'm trying to access an object who's index is outside my array.
Futhermore, in my tick function I ran the code:
int gVal = 1;
int gIndex = 0;
while (gIndex < [theGrid count]) {
[[theGrid objectAtIndex:gIndex] getValue:&gVal];
gIndex += 1;
}
I did not get an exc_bad_access error. Please help me figure out why I'm getting an exc_bad_access error.
EDIT:
I split [[theGrid objectAtIndex:(xSpot+ySpot*120)] getValue:&gridValue]; into:
id object = [theGrid objectAtIndex:(xSpot+ySpot*widthInGridSize)];
gridValue = [object intValue];
I still get exc_bad_access and it says it is on the line:
gridValue = [object intValue];
So I assume this means object has already been released? I don't understand how that's possible. I thought ints didn't need to be retained in any way since they're just ints. Also I thought adding an object to an array automatically retained it so why would my int get released.
In the debug section the value of object is said equal: (_NSCFNumber *) 0x005aec80 (int) 0
As per comments - this isn't actually an out of bounds issue, but rather the object you're pulling out of 'theGrid' is bogus (already released). Break that into multiple lines to confirm; and turn on "zombies" in your debug settings. Cheers!
I've been working on a game, and at a point I didn't have issues removing the enemy game objects (right now they are subclassed from CCSprite and I know that's not the best way)
But I'm not sure what I changed to make it crash when the program attempts to removeChild from _targets after they have been added to targetsToDelete.
I tried moving things around, I just don't know how I am adding or editing the array while its being created... Any help or advice would be great!
And actually if you had any pointers on how best to create game enemies, do you subclass NSObject or CCNode? I heard to divide them into component classes but I had no clue what they meant.
//Projectile Target collision
-(void)update:(ccTime)dt {
for (spygot *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
//Collision Detection Player
CGRect playerRect2 = CGRectMake(
_controlledSprite.position.x - (_controlledSprite.contentSize.width/2),
_controlledSprite.position.y - (_controlledSprite.contentSize.height/2),
_controlledSprite.contentSize.width,
_controlledSprite.contentSize.height);
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (Projectile *projectile in _projectiles)
{
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
BOOL monsterHit = FALSE;
if (CGRectIntersectsRect(projectileRect, targetRect))
{
NSLog(#"hit");
target.mhp = target.mhp - 1;
monsterHit = TRUE;
if (target.mhp <= 0)
{
[targetsToDelete addObject:target];
}
}
for (spygot *target in targetsToDelete)
{
[self removeChild:target cleanup:YES];
[_targets removeObject:target];
}
if (monsterHit)
{
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (Projectile *projectile in projectilesToDelete)
{
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
It looks like the code that you've pasted is all from within a for loop iterating over _targets. How does the variable target get initialized?
Usually when I get this sort of error it's because I have the code in a block or am in some other way on a nebulous thread. How sure are you that this bit of code is not running more than once at the same time?
You could try wrapping it in the following:
dispatch_async(dispatch_get_main_queue(), ^{
// do everything here.
});
As for advice about using CCSprite for your game enemy objects, my advice is fix it when it becomes a problem. Are you seeing issues with it right now? Premature optimization is almost as bad as doing it wrong in the first place. You'll know better at the end of the project how you should have done it earlier. ;)
I guess you know that you cannot remove elements from the array while you iterate over it. That is why you have targetsToDelete array.
But it looks to me that you do remove targets to soon.
Try this:
finish iterating the main loop and finish collecting targets to the targetsToDelete array and only after main loop is done remove the targets.
So, here's how it goes.
I am currently working on Cocos2d game, which consists of many Obstacles. One obstacle gets added on the screen at an interval of 10 seconds like this.
ObstacleSprite* newObstacle = [ObstacleSprite spriteWithFile:#"Obstacle.png" rect:CGRectMake(0, 0, 20, 20)];
newObstacle.position = ccp(mainPlayer1.position.x,10);
[self addChild:newObstacle];
[self.arrayForObstacles addObject:newObstacle];
Now, I insert these obstacles into the arrayForObstacles because I also want to keep checking whether the Obstacles and MainPlayer don't collide.
I check it with the help of this function.
- (void) checkCollisionWithObstacle
{
if(mainPlayer1.playerActive)
{
for(int i = 0; i < [self.arrayForObstacles count]; i++)
{
ObstacleSprite* newObstacle = [self.arrayForObstacles objectAtIndex:i];
if(newObstacle != nil)
{
if(CGRectIntersectsRect([mainPlayer1 boundingBox], [newObstacle boundingBox]))
{
mainPlayer1.livesLeft--;
}
}
}
}
}
THE ISSUE
Problem is when I get to certain score, one of the Obstacles gets deleted. Removal of Obstacles works as in First In-First Out (FIFO) mode. So, to delete obstacles, I write the following method :
- (void) keepUpdatingScore
{
//update new score
mainPlayer1.score+=10;
//remove obstacle when score increases by 5k
if(mainPlayer1.score > 5000 && mainPlayer1.score > 0)
{
mainPlayer1.playerActive = NO;
if([self.arrayForObstacles count] > 0)
{
CCLOG(#"count is %d",[self.arrayForObstacles count]);
ObstacleSprite* newObstacle = [self.arrayForObstacles objectAtIndex:0];
[self.arrayForObstacles removeObjectAtIndex:0];
[self removeChild:newObstacle cleanup:YES];
CCLOG(#"count is %d",[self.arrayForObstacles count]);
}
mainPlayer1.playerActive = YES;
}
else
{
}
It crashes when score crosses 5000 mark!
UPDATE
Crash happens when it again goes to the method checkCollisionWithObstacle.
This is the THREAD Look.
THis is the line Which crashes.
you seem to be using mainPlayer1.playerActive as a semaphore to block checking the checkCollisionWithObstacle loop from a delete in the keepUpdatingScore method (are they asynchronous ?). Assuming they are, the way you are blocking the loop access wont work if the code enters keepUpdatingScore AFTER the loop started in checkCollisionWithObstacle ... your mileage will vary.
Is it possible to pass [self anyFunction] in blocks without a __weak object from self?
As an example this is valid code from the System Framework:
[UIView animateWithDuration:0.8 animations:^{
//Do animationStuff
} completion:^(BOOL finished) {
[self anyFunction];
}];
You can pass [self anyFunction] in the completion block without a warning. But if you write your own method with a completion block, the following warning occurs: capturing 'self' strongly in this block is likely to lead to a retain cycle.
A working solution is quite simple (iOS 5 + ARC). Before the block declare:
__weak MyClass *weakSelf = self;
and in the completion block you have to call:
[weakSelf anyFunction];
But, back to my Question: Why there is no need in the System Framework APIs to use a __weak object and to use self without any warnings. And how to implement a method without the need of a __weak object in the block?
Thank you for your effort.
The blocks which throw up the error are ones where you capture the objects that own the block. For example
[object performBlock:^{
[object performSomeAction]; // Will raise a warning
}];
or
[self performBlock:^{
[self doSomething]; // Will raise a warning
}];
but
[self performBlock:^{
[object doSomething]; // <-- No problem here
}];
Because an object retains its blocks, and a block retains it's objects. So in both these cases, the object which performs the block owns the block, which also owns the object. So you have a loop - a retain cycle. which means the memory is leaked.
In the example you have given - you're looking at a class method. You're calling the block on a UIView class, not a UIView object. A class has no memory associated with it. And you are probably calling this function from a controller, so the self reference is being retained by the block, but there is no loop because self is not retaining the block.
In the same way that, you may have noticed, not all objects that are used in the block need to be weakly referenced - just the ones that cause a retain cycle.
On code that I need to compile potentially with or without ARC, or with or without the newer compilers, I do the following ... functionally it's the same as what you've listed already, but it avoids the__weak and also avoids the retain release cycles:
//
// FOR NON-ARC PROJECTS
//
__block __typeof__(self) bself = self;
[someObject doThingWithBlock:^(id result){
if (!bself)
return;
bself.thingWhich = result;
}];
///
// FOR ARC PROJECTS
//
__weak MyClass *bself = self;
[someObject doThingWithBlock:^(id result){
if (!bself)
return;
bself.thingWhich = result;
}];
I'm making a game with cocos2d and I have a bunch of sprites that I would like to delete. For example I might have a bunch of characters on the screen but when my game is over I would like to clean them up. Right now I have created a special effect (particle system) as a distraction, but because it is transparent and does not cover all of the screen you can see through and watch the sprites disappear as I remove them from the layer.
Also because the instructions execute so fast to the user it appears as if the sprites disappear before the particle effect begins!
Any suggestions on my 2 problems? Thanks.
NSMutableArray *toRemove = [[NSMutableArray alloc] init]; // array of sprites that I collect to remove
spriteCount = 0;
if([self findAllSprites:parent forRemoval:toRemove] > 0){ // is there is at least one sprite to delete. If not then don't do anything
[self specialEffect]; // runs for maybe 3 seconds.
// how can I stall here so that the sprites aren't removed "instantaneously"?
for (Character* aCharacter in toRemove) {
[aCharacter.parent remove:aCharacter];
}
}
You can delay the removal action using performSelector:withObject:afterDelay:. For example:
NSMutableArray *toRemove = [[NSMutableArray alloc] init]; // array of sprites that I collect to remove
spriteCount = 0;
if([self findAllSprites:parent forRemoval:toRemove] > 0){ // is there is at least one sprite to delete. If not then don't do anything
[self specialEffect]; // runs for maybe 3 seconds.
[self performSelector:#selector(removeSprites:) withObject: toRemove afterDelay:1.0];
}
[toRemove release];
- (void) removeSprites: (NSArray*) toRemove
{
for (Character* aCharacter in toRemove) {
[aCharacter.parent remove:aCharacter];
}
}
Note that performSelector:withObject:afterDelay: will retain the toRemove object and keep it alive until after it calls removeSprites, so you don't have to do anything special with toRemove (except that you still need to release it as shown since you own it as well).
You need to do your 'special effect' in a thread, so that it runs alongside your sprite remove. Lookup NSThread for more information, but this will enable you to synchronize the two processes.