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

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

Related

How to create an array of non-retained objects in arc?

How to create an array of non-retained objects in arc? These objects are observers in this array. Currently, I'm creating this array in this way:
_observers = CFBridgingRelease(CFArrayCreateMutable(NULL, 0, NULL));
The problem is the code crashes sometimes when making notifications in this line:
for (NSInteger i = [_observers count] - 1; i >= 0; i--) {
// crash line
id<ListModelObserver> observer = (id<ListModelObserver>)[_observers objectAtIndex:i];
...
I have zombies enabled on, and clearly see observer object classname in debug console. The observer object should be already removed from _observers during dealloc... The only thing that comes to my mind is _observers array somehow retains its objects. Any ideas?
You're releasing the array, not the objects. If you want to have a collection of unsafe unrestrained pointers to objects, then either use a C array or set up a CFArrayRef that doesn't include any call back functions.

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.

iPhone game: enumerating over enemies

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]
}
}

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];
}

Detect if one position in Array is already initiated

I need to check specific positions in an NSArray to see if they have already been initialized, but I am having trouble. I tried to do the following, but it causes my application to crash!
if ((NSMutableArray *)[arrAllBlocks objectAtIndex:iLine] == nil)
{
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
What is the best to do this? I already tried some methods like isValid, and things like that!
You have a few options here:
Option 1: Pre-fill the array with instances of NSNull, and then use the code given by Dave DeLong in his answer.
Option 2: (Similar to #1) pre-fill the array with instances of NSMutableArray, and then have no extra code at all. (If you're going to pre-fill, you may as well do this).
Option 3: Do not pre-fill the array, but insert items dynamically as required. This will be almost identical to a pre-fill if the first iLine is near the maximum:
while([arrAllBlocks count] <= iLine)
{
[arrAllBlocks addObject:[NSMutableArray arrayWithCapacity:0]];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
Option 4: Use a dictionary to maintain the list of NSMutableArrays:
NSString *key = [NSString stringWithFormat:#"%d", iLine];
NSMutableArray *columnArray = [dictAllBlocks objectForKey:key];
if (columnArray == nil)
{
columnArray = [NSMutableArray arrayWithCapacity:0];
[dictAllBlocks setObject:columnArray forKey:key];
}
[columArray insertObject:newBlock atIndex:iColumn];
How to choose:
If the maximum value for iLine is not enormous, I would go with option #2. A handful of NSMutableArrays initialized to zero capacity will take up very little memory.
If the maximum value for iLine is enormous, but you expect it to be accessed sparsely (i.e., only a few values of iLine will ever be accessed), then you should go with Option #4. This will save you from having to fill an NSMutableArray with objects that never get used. The overhead of converting the string-value key for the dictionary will be less than the overhead for creating all of those blanks.
If you're not sure, try out each option and profile them: measure your memory usage and the time required to execute. If neither of these options work, you may have to explore more complex solutions, but only do that if it turns out to be necessary.
A note of caution:
The original code that you posted has a memory leak in the following line:
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
The NSMutableArray objects that you initialize here are never released. When you call [[NSMutableArray init] alloc], a brand new object is created (with a reference count of one). The insertObject method then adds that new object to arrAllBlocks, and retains it (increasing its retain count to 2). Later, when you release arrAllBlocks, the new array will be sent a release message, but that will only reduce its retain count to one again. At that point, it will stick around in RAM until your program exits.
The best thing to do here is to use [NSMutableArray arrayWithCapacity:0] instead (as I have done in my examples). This returns a new NSMutableArray, just the same as your code did, but this instance has already been autoreleased. That way, arrAllBlocks can take ownership of the new object and you can be sure that it will be released when appropriate.
You can't. NSArray (and its subclass NSMutableArray) do not allow you to insert nil into the array. That's clearly outlined in the documentation.
If, for some reason, you need to have "empty" values in an array, then you should insert [NSNull null] instead and test for that. From the docs: "The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values)."
UPDATE:
This means you could change your code very simply to this:
if ([[arrAllBlocks objectAtIndex:iLine] isEqual:[NSNull null]]) {
[(NSMutableArray *)arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = (NSMutableArray *)[arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
To check for NSNull you can simply compare against the pointer, since it's a Singleton:
if ([NSNull null] == [arrAllBlocks objectAtIndex:iLine]) {
[arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = [arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
I also removed the unsightly casts. Casting is rarely necessary in Objective-C. It usually just adds noise, and can hide real bugs. Since you're experiencing crashes, it's worth removing the casts from this code and listen to what the compiler has to tell you about it.
Telling the compiler to ignore warnings for a piece of code does not make the underlying problem with it go away!