iPhone game: enumerating over enemies - iphone

So I'm constantly checking collision detections and whenever an enemy gets killed I remove it from the array I'm iterating over.
This is giving me enmueration mutation exceptions, what is the normal way to solve this?
People talked about making copies of the array, but when I'm iterating over it every fraction of a second that idea seems ridiculous.

I agree with the comment of Till. Do something like that, if self.enemies is your original mutable array
NSArray * enemiesToRemove = [[NSMutableArray alloc] init];
for (Enemy * enemy in self.enemies) {
if ([enemy colidesWithBullet]) {
[enemiesToRemove addObject:enemy];
}
}
[self.enemies removeObjectsInArray:enemiesToRemove];
[enemiesToRemove release];

Basically you cant remove or add from the array while its being enumerated, and since you seem to be iterating the array often then you will probably get this exception a lot, one choice is to lock your array while you iterate and while you remove using a #synchronized block on the array, this will guarantee that you wont be modifying the array while iterating...Down side to this approach is that you will have the iterations and add/remove operations waiting on each other...
#synchronized(myArray)
{
//iterate through myArray
}
#synchronized(myArray)
{
//mutate the array
}

Removing items from an array that you are itterating over can be tricky. You can do something like Zoleas suggested or you can avoid enumerating the list and start at the last element and check backwards to the first element removing elements that need removed. This way you can ensure that you are never removing an element that will effect the index of later elements.
for(int i=[array count]-1; i >=0 :i--)
{
bool needsRemoved = /* your code here */;
if (needsRemoved)
{
[ary removeObjectAtIndex:i]
}
}

Related

How to safely loop over a mutable array while modifying it?

I have a controller which has an array holding actors. An actor is a object which will be called by the controller.
The problem: The controller iterates over the actors array and sends each actor an -actionMessage. The actor can create and register another actor with the controller, or remove an actor or even itself from the controller's actors array. It is routed through two methods:
-registerActor:(Actor*)actor;
-unregisterActor:(Actor*)actor;
So while the controller iterates over the actors array, the list of actors can change.
Edit: And any newly added actor MUST go through the loop as well.
What is best practice to deal with this problem? Should I create a copy of the actors array before iterating over it?
Create a copy of your mutable array and iterate over that.
NSArray *loopArray = [NSArray arrayWithArray: yourActorArray];
Or
NSArray *loopArray = [yourActorArray copy];
//in this case remember to release in nonARC environment
This is what I usually do...
NSMutableArray *discardedItems = [NSMutableArray array];
SomeObjectClass *item;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
hoping this helps.
The standard procedure for enumerating through a mutable array that needs to be altered as you step through it is to make a copy and iterate through that, altering the original. I'm guessing the NSMutableArray of actors is a property belonging to your controller, and that registerActor: and unregisterActor: both alter this array. If you step through a copy you can remove actors from the original property through the methods without altering the copy.
NSMutableArray *actorArrayCopy = [self.actorArray copy];
for (id object in actorArrayCopy){
//do stuff
}
In some cases, you can scrap fast enumeration and use a standard for loop, however, this is risky here. If objects are inserted or removed from the array, the indexes will shift (AFAIK), meaning you may end up skipping elements or going over an element multiple times.
Some people store elements to be altered (such as removed) in a separate array within the fast enumeration and perform all the changes at once once the enumeration is done, but you are using separate methods for adding and removing elements; the elements themselves determine what should happen and notify you. This would make things more complicated, so a copy will probably work best.
You can avoid making a copy of the array by doing this:
for(int i=0; i<[array count]; i++)
{
if(condition)
{
[array removeObjectAtIndex:i];
i --;
continue;
}
}
You should use a set instead of an array. Then you can copy the set, and after the operation is done, take a diff to see whats changed.
I've made a macro called smartFor that allows you to do the same set up as a for-loop fast enumeration but handles modification of array. It takes advantage of a few C and CLANG exploits and is very simple and clean.
Macro:
///Allows for modifying an array during virtual fast enumeration
#define smartFor(__objectDeclaration, __array, __block) {\
int __i = 0;\
while (__i < __array.count) {\
__objectDeclaration = __array[__i];\
NSObject *__object = __array[__i];\
[__block invoke];\
if ([__array indexOfObjectIdenticalTo:__object] != NSNotFound && ((int)[__array indexOfObjectIdenticalTo:__object]) <= __i) {\
__i = ((int)[__array indexOfObjectIdenticalTo:__object])+1;\
}\
}\
}
It automatically handles all of the [basic] wonky things you may do like adding objects (anywhere in the array, at any point during the "fast enumeration"[1]), removing objects, adjusting the index of objects, modifying objects, etc.
Example Usage:
smartFor(NSNumber *aNumber, myArray, ^{
if ([aNumber isEqualToNumber:#(3)]) {
[myArray removeObjectIdenticalTo:aNumber];
}
});
[1]Note: It's not technically fast enumeration on a strict definition/performance basis, obviously, but behaves just like it for all intents and purposes

More efficient way to iterate through an array of NSStrings and compare them

I'm just looking for a nicer and more efficient way to iterate through a given array of objects and compare a NSString property of each to another array just containing NSStrings.
My current code uses two for-each loops but it don't think that it is the most efficient way.
for (MYClass *foo in arrayOfMyClass) {
for (NSString *ID in arrayOfStringIDs) {
if ([foo.Id isEqualToString:ID]) {
//Do something
break;
}
}
}
I think that it should be somehow possible to drop at least one loop with some cool tricks.
If all you want to know is if foo.Id exists in arrayOfStringIDs, use an NSSet of strings instead. Then you can do:
NSSet * mySetOfStringIDs = [NSSet setWithArray:arrayOfStringIDs];
for(MyClass * foo in arrayOfMyClass) {
if([mySetOfStringIDs containsObject:foo.Id]) {
// Do something
break;
}
}
This avoids the second loop, since containsObject: is generally much faster than O(n) for a set. You should, of course, do your own profiling as needed.
Check for indexofobject method of Nsarray. May be it can help you to get the index directly instead of a loop for the string in nsarray.
If you want to get an array of strings that exist in both arrayOfMyClass and arrayOfStringIDs then you could use key-value coding to pull the set of strings out of arrayOfMyClass and intersect the resulting set with arrayOfStringIDs. If your class is KVC compliant then you can get all the Id strings out of it as a set:
NSMutableSet *idSet=[NSMutableSet setWithArray:[arrayOfMyClass
valueForKeyPath:#"#distinctUnionOfObjects.Id"]];
[idSet intersectSet:[NSSet setWithArray:arrayOfStringIDs]];
NSArray *idArray=[idSet allObjects];
Unfortunately there is not a method to intersect two NSArrays which is why they have to be turned into a set first.

removing items from NSmutableArray

I have two nsmutablearrays
array1: searchedStores = [[NSMutableArray alloc] init];
array2: allStores; ///it holds Stores information and it is also
NSMutableArray
Now if i add objects in "searchedStores" Array
for (int i= 0; i < [allstores count]; i++){
Franchise *store = [allStores objectAtIndex:i];
if ([store.ZipCode hasPrefix:str]) /// where str is searched string
{
[searchedStores addObject:store]; // some stores information will be inserted in "searchedStores" array
}
}
Now if i remove objects from searchedStores array it will also remove objects from "allStores" array
to remove objects i am writing
[searchedStores removeAllObjects];
How can i remove objects only from "searchedStores" array. and "allStores" Array should keep its objects.
IF you are just assigning allstores array with searchedstores array then changes made in searched stores array will be reflected to allstores. You need to allocate new memory to allstores and then add objects from searchedstores array to it upon your logical behavior.
Every array is totally independent. If removing objects from one array effects the other, you don't have two arrays, but only two pointers to the same array. You need to instantiate (alloc/init or mutableCopy) both. Feel free to show your initialization code as well.

copying object from NSMutableArray to another using IF statement

OK, maybe I'm not seeing clear anymore and hope you can help.
I'm trying to select an Object from a NSMutableArray using:
if([car.seat isEqualToString:#"fancyOne"]){
fancyThings = [[NSMUtableArray]init];
[fancyThings addObjects: car];
}
Now I forgot to tell you I'm new at this Objective-C, so maybe I'm thinking the wrong way.
What I'm basically trying to do is to get an Object from one array by selecting a value of it's components.
This is the way to do it, I am however keep having trouble with my if-statement.
If I leave out the IF-statement it does fill my other NSMutableArray with the exact same object (thisCar) but if I put in the IF-statement it doesn't pick up that the string is the same in thisCar.seat.
I next example it puts everything in the normalThings but there are some aCar.seats which contain the string FANCYONE. I checked the XML file on spaces and that sort of things but everything is in order as far as I can see.
Shall I build it using NSScanner instead of IsEqualToString?
- (void)viewDidLoad {
appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.fancyThings = [[NSMutableArray alloc]init];
for (CARS *aCar in appDelegate.someCars) {
if ([aCar.seats isEqualToString:#"FANCYONE"]){
[appDelegate.fancyThings addObject:aCar];
}
else {
[appDelegate.normalThings addObject:aCar];
}
}
self.title = #"Cars";
super viewDidLoad];
}
EDIT:
My BAD!! The code supplied was in fact in order!
There was a mistake in my XMLParser, which added blank lines to the strings, so I couldn't get an equal string!
Hopefully this will give you some guidance:
//init new array
NSMutableArray *fancyThings = [[NSMutableArray alloc] init];
//walk your array
for (SomeCarObject *thisCar in arrayOfCars) {
//is thisCar a qualifying object
if ([thisCar.seat isEqualToString:#"fancyOne"]) {
//yes, add thisCar object
[fancyThings addObject:thisCar];
}
}
You'll want to create that NSMutableArray outside of the for loop (assuming you're iterating through a collection). Then you can add to that NSMutableArray like you did.
Hope this helps!
BTW, you should edit your question with the comment you made to elaborate on it..
It's depends from volume of objects, which u deal with. If there is 1000 objects or less, this method looks good. But if there is more objects, u have risk to freeze u application and have a big memory leaks.
Also if u will need concurrency code later, u have to keep in u mind some
other solutions.
U can using not just a string objects in u array, u can try to fill u array after application startup in objects, which response if string is same or not. Or using nsdictionary with appropriate keys.
Please read my post multithread search design

problem with listener pattern using fast enumeration

Hey, right now I have implemented my own listener pattern.
I will send an update to the listeners using fast enumeration.
the code will look like this
- (void) updateListeners {
for (id<AProtocol>listener in _listeners)
{
[listener update];
}
and in listener, i implement method for AProtocol, which is update.
suppose there are n object in _listeners, and m number of listener such that m < n want to remove it self from listen when listener's update method is called.
The problem with this is that I can't remove when the fast enumeration is ongoing, I will get an error.
In order to make the listener more dynamic so that we can remove listener from _listeners when update method is called, what would be the solution?( I don't want to use NSNotificationCenter)
It sounds like what you have now is the listener itself deciding whether it should be removed, and removing itself. That's problematic because (a) as you say, it breaks your enumeration, but (b) because it's a tricky abstraction-- if the object that runs "update" doesn't also control ownership in the listener list directly, your design pattern might run into problems anyways. I might suggest that you redefine update listeners like this:
- (BOOL)update
and return a BOOL indicating whether the listener should be removed (or kept, depending on your semantics). Then you could write the loop like this:
NSMutableSet * listenersToBeRemoved = [NSMutableSet set];
for (id<AProtocol> listener in _listeners) {
BOOL shouldRemove = [listener update];
if (shouldRemove) {
[listenersToBeRemoved addObject:listener];
}
}
// Do this if _listeners is a Set, or whatever the equivalent is.
[_listeners minusSet:listenersToBeRemoved];
As others have suggested, if you do want to allow the listeners to remove themselves during the update process, it's simple enough to just iterate through a local copy of the collection, instead of the collection itself. The syntax for that depends on whether _listeners is an array, a set, or something else, but see other answers or the docs.
Why not operate the enumeration on a copy of the array?
for (id<AProtocol>listener in [NSArray arrayWithArray:_listeners])
{
[listener update];
}
Then _listeners can safely be modified during the loop. It's safer than Davids solution since it's immune against any listener removals not only the ones that happen in -update.
Replace fast iteration by usual iteration and start from the last.
// must iterate from the last in case the current listener removes itself from the list
for (int i = [_listeners count] - 1; i > -1; i--) {
id<AProtocol> listener = [_listeners objectAtIndex:i];
[listener update];
}