how to remove objects from NSMutableArray correctly? - objective-c

I try to delete items from NSMutableArray in loop. I have an array :(2,3,4,5,6).
int j = [array count];
while (array != NULL) {
NSUInteger g = 0;
for (int q = 0; q < j; q++) {
[array removeObjectAtIndex:g];
}
When i set a breakpoint (after first iteration) i got the following:
[0]=(id)0x00000000
[1]=(id)0x071421a0(int)3
[2]=(id)0x071421b0(int)4
[3]=(id)0x071421e0(int)5
I don't understand how to delete in each iteration a first object. I mean that following it will be first. And why the last one is disappeared every time also?
Thanks.

Another way to delete a collection of objects from an array is to add the objects you want to delete to a separate array, then use that to delete your objects from the primary array in one fell swoop. There are benefits to this, least of which would be no risk of out-of-bounds, and also the possibility for rolling back since you are essentially removing in a single batch process:
int j = [array count];
NSMutableArray *theseObjects = [NSMutableArray array];
for (int q = 0; q < j; q++)
{
id thisObject = [array objectAtIndex:q];
BOOL shouldRemoveThisObject = ...//<--determine if you want to remove this object
if (shouldRemoveThisObject)
[theseObjects addObject:thisObject];
}
[array removeObjectsInArray:theseObjects];

It looks like you want to remove all entries. Then you could also call [myArray removeAllObjects];

You cant do as you are trying to do.
j is set to 5 (array count) but the array's size is reducing on every iteration. So at one point of time it will try to access array out-of-bound, hence exception and your app will crash.
Instead you need to do as below:
while (array.count > 0) {
NSLog(#"Arr before removal is : %#",array);
[array removeObjectAtIndex:0];
NSLog(#"Arr after removal is : %#",array);
}
If for some other reason you want to remove only first then the above will work.
If you want to remove all the objects at one go: you can use:
[array removeAllObjects];

You CAN'T remove objects from NSMutableArray inside a loop. This is because, in that case, array size is changing over each iteration (worst case) and you could try to access to an out of bounds index.
The best option is Jeremy's solution. Keep desired objects on separated array and then remove these objects from the main array, outside of the loop.

Related

NSMutableArray was mutated while being enumerated

I have an array in an old objective-C app that I am using to learn more "complicated" coding. It is back from the old days of OS X and was very much broken. I have gotten it to work (mostly)! However, the app has an NSMutableArray of images, 7 in total. I use a random number generator to insert the images on the screen, some code to allow them to fall, and then, using screen bounds, when they reach "0" on the Y axis they are removed from the array.
I initially just had:
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
[flakesArray removeObject:myItem];
I have read when removing objects from an array it is best practice to iterate in reverse...so I have this bit of code:
for (NSInteger i = myArray.count - 1; i >= 0; i--)
{ //added for for statement
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
[myArray removeObjectAtIndex:i];
}
Sadly both methods result in the same mutated while enumerated error. Am I missing something obvious?
If I add an NSLog statement I can get, I think, the index of the item that needs to be removed:
NSLog (#"Shazam! %ld", (long)i);
2017-01-07 14:39:42.086667 MyApp[45995:7500033] Shazam! 2
I have looked through a lot and tried several different methods including this one, which looks to be the most popular with the same error.
Thank you in advance! I will happily provide any additional information!
Adding more:
Sorry guys I am not explicitly calling NSFastEnumeration but I have this:
- (void) drawRectCocoa:(NSRect)rect
{
NSEnumerator* flakesEnum = [flakesArray objectEnumerator];
then
for( i = 0; i < numberToCreate; i++ )
{
[self newObject:self];
}
while( oneFlake = [flakesEnum nextObject] )
It is here where:
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
NSLog (#"Shazam! %i", oneFlake);
[flakesArray removeObject:oneFlake];
}
Thank you all. I am learning a lot from this discussion!
There are two ways to go: (1) collect the objects to remove then remove them with removeObjectsInArray:.
NSMutableArray *removeThese = [NSMutableArray array];
for (id item in myArray) {
if (/* item satisfies some condition for removal */) {
[removeThese addObject:item];
}
}
// the following (and any other method that mutates the array) must be done
// *outside of* the loop that enumerates the array
[myArray removeObjectsInArray:removeThese];
Alternatively, reverseObjectEnumeration is tolerant of removes during iteration...
for (id item in [myArray reverseObjectEnumerator]) {
if (/* item satisfies some condition for removal */) {
[myArray removeObject: item];
}
}
As per the error, you may not mutate any NSMutableArray (or any NSMutable... collection) while it is being enumerated as part of any fast enumeration loop (for (... in ...) { ... }).
#danh's answer works as well, but involves allocating a new array of elements. There are two simpler and more efficient ways to filter an array:
[array filterUsingPredicate:[NSPredicate predicateWithBlock:^(id element, NSDictionary<NSString *,id> *bindings) {
// if element should stay, return YES; if it should be removed, return NO
}];
or
NSMutableIndexSet *indicesToRemove = [NSMutableIndexSet new];
for (NSUInteger i = 0; i < array.count; i += 1) {
if (/* array[i] should be removed */) {
[indicesToRemove addIndex:i];
}
}
[array removeObjectsAtIndexes:indicesToRemove];
filterUsingPredicate: will likely be slightly faster (since it uses fast enumeration itself), but depending on the specific application, removeObjectsAtIndexes: may be more flexible.
No matter what, if you're using your array inside a fast enumeration loop, you will have to perform the modification outside of the loop. You can use filterUsingPredicate: to replace the loop altogether, or you can keep the loop and keep track of the indices of the elements you want to remove for later.

NSMutable array count not changing after removed child

GMSprite *bulletMove;
int bulletCount = [bullets count];
for(int i = 0; i < bulletCount; i++)
{
if(bulletMove.position.x > 500)
{
[self removeChild:[bullets objectAtIndex:i] cleanup:YES];
}
}
How do i remove the child from the array and also the object in the array so that bulletCount goes down an integer and adjusts the array to the removed object
Use below code:
[bullets removeObjectAtIndex:i];
Get bullet object from bulet array and use below code.
[bullet removeFromParentAndCleanup:YES];
Use
[bullets removeObjectAtIndex:i];
How do i remove the child from the array and also the object in the
array so that bulletCount goes down an integer and adjusts the array
to the removed object
Using the above method will remove the object from index i and all following objects will shift one-up.
GMSprite *bulletMove;
for(int i = 0; i < [bullets count]; )
{
if(bulletMove.position.x > 500)
{
[bullets removeObjectAtIndex:i];
} else {
i++;
}
}
It is not exactly good style manipulating the index varialbe of a for loop within its body. You may want to re-structure this suggestions with antother type of loop (do-while or so). The basic idea is, however, that [bullest count] will always provide you with the current amount of entries in the array. And the index must only be increased if you do not remove the current object. If you remove it an your index is at 10 (example) then the next one to be checkt is at 10 again. If you remove that too then the next one to be checked against the 500 is agein at 10. So either remove it or increase the index. And as exit criteria of the loop check the index against the current amount of objects in the array.
Edit: Second part of your question: If you do your memory management right, regardless wether you ARC or not, removeObjectAtIndex should properly remove the object itself. (Unless its retain count was higher than 1 or another strong reference still exists. But even then it reduces the retain count by 1 and does exactly the right thing.)

NSMutableArray cannot remove duplicates

I have duplicates in my array and i want to get rid of them, so i run this loop, however it doesn't work. Any one know why?
The array currently has 3 items, 2 duplicates and 1 unique.
for (int x = 0; x <= [array count]; x++) {
if(x > 0){
if([[array objectAtIndex:x - 1] isEqualToString:[array objectAtIndex:x]]){
[array removeObjectAtIndex:x];
}
}
}
You can't iterate over an object and modify it at the same time. Once you remove an object, the indexes of all the objects change. You can try copying the array first and iterate that and make the modifications in the original array, but you still might have to change some of your logic depending on what you're trying to accomplish.
Your algorithm only ever compares items that are next to each other in the array (the items at positions x and x-1). If the duplicates are in any other positions, they won't be found.
The naïve way to fix this is to do a double loop. Compare each item in the array with every item after it. This will start taking an extremely long time as your array becomes bigger.
The correct way to do this is to let the framework handle the operation. Convert your array to a set (which does not have duplicates by definition) and then back to an array:
NSSet * s = [NSSet setWithArray:array];
NSArray * dedupedArray = [s allObjects];
If you need to preserve the order, you'll have to do this in a slightly roundabout way, although this is still faster than the double-loop:
NSMutableSet * itemsSeen = [NSMutableSet set];
NSMutableArray * dedupedArray = [NSMutableArray array];
for( id item in array ){
if( ![itemsSeen containsObject:item] ){
[itemsSeen addObject:item];
[dedupedArray addObject:item];
}
}
I would suggest simply using NSSet ( or NSMutableSet ). It will automatically ensure you have only one of every object.
BUT - notice it is one of every OBJECT. It can have 2 objects that are different but have the same inner value.
If you want to ensure that there are no duplicates in your array, it would be better to use an NSMutableSet rather than an NSMutableArray.
NSMutableSet maintains the invariant that every object in the set is unique.
For example:
NSMutableSet* set = [NSMutableSet set];
NSString* data = #"Data";
[set addObject:data];
[set addObject:data];
The second call to addObject: will do nothing as data is already in the set.

Removing object from NSMutableArray

I stumbled across the following shortcut in setting up a for loop (shortcut compared to the textbook examples I have been using):
for (Item *i in items){ ... }
As opposed to the longer format:
for (NSInteger i = 0; i < [items count]; i++){ ... } //think that's right
If I'm using the shorter version, is there a way to remove the item currently being iterated over (ie 'i')? Or do I need to use the longer format?
You cannot remove objects from array while fast-enumerating it:
numeration is “safe”—the enumerator
has a mutation guard so that if you
attempt to modify the collection
during enumeration, an exception is
raised.
Anyway why do you need to change you container while enumerating it? Consider storing elements that need to be deleted and remove them from your container using removeObjectsInArray: or removeObjectsAtIndexes: method.
Just add keyword break; after removing the item...
for(id item in items) {
if([item isEqual:itemToDelete]) {
[items removeObject:item];
break; // A very important line 🛑
}
}
An Objective-C collection must not be modified during enumeration.
You may use this variant to delete objects from collection:
for (NSInteger i = items.count - 1; i >= 0 ; i--) {
[items removeObjectAtIndex:i];
}
The former loop is a "for-each" loop in Objective C.
*i is a pointer to the direct item in the items-Array (most of the time this will be NSMutableArray).
This way you can operate directly on the item:
[items removeObject: i];
This (should) work - I am currently not working on my Mac and can't check it.
However it might be that Objective-C Prevents removing objects while iterating over the collection (that is quite common in most languages).
I use this code for this:
for (NSUInteger i = [items count] - 1; ; i--) {
[items removeObjectAtIndex:i];
}

Replacing multiple array contents with a single object?

I have an NSMutableArray with contents I want to replace with NSNull objects.
This is what I do:
NSMutableArray* nulls = [NSMutableArray array];
for (NSInteger i = 0; i < myIndexes.count; i++)
[nulls addObject:[NSNull null]];
[stageMap replaceObjectsAtIndexes:myIndexes withObjects:nulls];
How can I do this more efficiently?
Is there a way to enumerate an NSIndexSet, so I can replace the array content one by one?
Solved
Suggested method turns out to be 2x faster (avg 0.000565s vs 0.001210s):
if (myIndex.count > 0)
{
NSInteger index = [myIndex firstIndex];
for (NSInteger i = 0; i < myIndex.count; i++)
{
[stageMap replaceObjectAtIndex:index withObject:[NSNull null]];
index = [myIndex indexGreaterThanIndex:index];
}
}
You can use a for loop. Start with the first index, use indexGreaterThanIndex: to get the next index, and stop after you hit the last index.
Don't forget to account for an empty index set. Both the first and last index will be NSNotFound in that case. The easiest way is to test the index set's count; if it's zero, don't loop.
Also, what Jason Coco said about profiling. Don't worry too much about efficiency until your program works, and don't go optimizing things until you have run Shark (or Instruments, if that's your thing) and found exactly what is slow.
I realise this is a very old question but I'm posting here in case anyone else finds this question you could use:
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSLog(#"index: %d", idx);
[objectArray replaceObjectAtIndex:idx
withObject:newObject];
}];
Which is a lot more succinct.