Reputation: 816
I have a problem with three clases. The the first class is called Player
. This class has an NSMutableArray
inside it called units
. This array is made up of objects of the class Unit
. This class in turn, has an NSMutableArray
called bullets
. It works like this:
At a certain point the class Player
(it could just be the ViewController
instead) adds an object to the units
. Then, when an instance of Unit
is initialized, as a result of the above, it creates an NSTimer that is in charge of creating bullets every second.
The thing is, is crashes somewhere in the middle of this with a SIGABRT
that tells me that there was an exception because: Collection <__NSArrayM: 0xb1a2970> was mutated while being enumerated.
Also, I took away the line that created bullets and it stops crashing, proving that is the problem. What does that mean!
Here is a bit of executable code that might work:
ViewController.h (instead of player)
@interface ViewController : UIViewController
{
NSMutableArray *units;
NSTimer *updateTimer;
}
-(void)Update;
ViewController.m
@implementation ViewController
//methods...
- (void)viewDidLoad
{
//more default code
//Initialized array and adds one object with the default constructor for simplicity
units = [[NSMutableArray alloc] initWithObjects:[[Unit alloc] init], nil]
}
-(void)Update
{
for(Unit *unit in units)
{
[unit Update];
if(unit.deleteFromList)
[units removeObject:unit];
}
}
//More methods
@end
Unit.h
@interface Unit : NSObject
{
NSMutableArray *bullets;
NSTimer *bulletTimer;
boolean deleteFromList;
}
@property(readonly, assign)deleteFromList;
-(void)Fire;
-(void)Update;
Unit.m
@implementation Unit
@synthesize deleteFromList;
-(id)init
{
if(self)
{
bullets = [[NSMutableArray alloc] init];
bulletTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:NULL repeats:true];
deleteFromList = false;
}
return self;
}
-(void)Fire
{
[bullets addObject:[[Bullet alloc] init]];
}
-(void)Update
{
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[bullets removeObject:bullet];
}
if(certainCondition)
deleteFromList = true;
}
The bullet class will be omitted because the contents are irrelevant to what happens. Also, all the classes and constructors were shortened because the rest is useless for this example
EDIT:
Another thing I forgot to add is that the timer is created in an enumeration of the NSMutableArray units in the update method i'm about to add. I'm also adding a variable to Unit and Bullet that orders it to delete. The bullet update changes the position and also changes the deleteFromList variable
Upvotes: 0
Views: 2822
Reputation: 21464
There are two easy approaches - one is to create an array of objects to remove, and the other is to iterate over a copy of the original way. This second technique can be somewhat easier to read.
NSMutableArray *toRemove = [NSMutableArray array];
for (Bullet *bullet in bullets)
{
[bullet Update];
if (bullet.deleteFromList)
{
[toRemove addObject:bullet];
}
}
[bullets removeObjectsInArray:toRemove];
for (Bullet *bullet in [bullets copy])
{
[bullet Update];
if (bullet.deleteFromList)
{
[bullets removeObject:bullet];
}
}
The first approach is slightly more verbose, but won't make a copy of the original array. If the original array is very large, and you don't want to make a copy of it for performance/memory reasons, the first approach is best. If it doesn't matter (99% of the time), I prefer the second approach.
As an aside, you shouldn't start a method name with a capital letter in Objective-C unless the method name starts with an abbreviation, e.g. URL
, so [bullet Update]
should really be renamed to [bullet update]
.
Upvotes: 4
Reputation: 680
Or
NSIndexSet *indexSet = [units indexesOfObjectsPassingTest:^BOOL(Unit *udit, NSUInteger idx, BOOL *stop) {
[unit Update];
return unit.deleteFromList;
}];
[units removeObjectsAtIndexes:indexSet];
Upvotes: 0
Reputation: 19307
You can't remove any item in NSMutableArray
while for-loop or enumerating it.
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[bullets removeObject:bullet];
}
to
NSMutableArray *toRemove = [NSMutableArray array];
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[toRemove addObject:bullet];
}
[bullets removeObjectsInArray:toRemove];
Upvotes: 4