简体   繁体   中英

iOS: Remove NSArray in NSMutableArray in For Loop

This is a pretty simple concept, but I'm not getting the results I'm wanting. I have an NSMutableArray that is populated with NSArrays, I want to loop through that NSMutableArray and remove certain NSArrays based on a key-value pair. My results have many of the NSArrays that I should be removing and I think it has something to do with the count of the NSMutableArray and the int I declare in the For Loop.

Here is my code: (restArray is the NSMutableArray)

for (int i=0; i<restArray.count; i++) {
    NSArray *array = restArray[i];
    if ([[array valueForKey:@"restaurant_status"] isEqualToString:@"0"]) {
        [restArray removeObjectAtIndex:i];
    }
}

Does someone know what I am doing wrong here?

It is not recommended to modify an array on what are you currently iterating.

Lets create a tmp array, and reverse your logic.

NSMutableArray * tmpArray = [NSMutableArray array];
for (int i=0; i<restArray.count; i++) {
    NSArray *array = restArray[i];
    if (![[array valueForKey:@"restaurant_status"] isEqualToString:@"0"] {
        [tmpArray addObject:array];
    }
}

So at the end of the iteration, you should end up with tmpArray having the arrays you needed.

Use NSPredicate :

NSArray *testArray = @[@{@"restaurant_status" : @"1"}, @{@"restaurant_status" : @"0"}];
NSArray *result = [testArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(restaurant_status == %@)", @"1"]];

When you remove an element all the elements past it shift down by one, eg If you remove the element at index 3 then the element previously at index 4 moves to index 3.

Every iteration you increase the index by one.

Combine the above two and you see that when you remove an element your code skips examining the following element.

The simple solution is to reverse the order of the iteration:

for (int i = restArray.count - 1; i >= 0; i--)

and then your algorithm will work.

Addendum

You can safely ignore this addendum if your arrays contain < 2^32 elements and you use Clang or GCC (and most other C compilers).

It has been raised in the comments that this answer has a problem if the array has 0 elements in it. Well yes & no...

First note that the code in the question is technically incorrect: count returns an NSUInteger which on a 64-bit machine is a 64-bit unsigned integer, the loop variable i is declared as an int which is 32-bit signed. If the array has more than 2^31-1 elements in it then the loop is incorrect.

Most people don't worry too much about this for some reason ;-) But let's fix it:

for (NSInteger i = restArray.count - 1; i >= 0; i--)

Back to the problem with an empty array: in this case count returns unsigned 0, C standard arithmetic conversions convert the literal 1 to unsigned , the subtraction is done using modular arithmetic, and the result is unsigned 2^64-1.

Now that unsigned value is stored into the signed i . In C converting from signed to unsigned of the same type is defined to be a simple bit-copy. However converting from unsigned to signed is only defined if the value is within range, and implementation defined otherwise.

Now 2^64-1 is greater than the maximum signed integer, 2^32-1, so the result is implementation defined . In practice most compilers, including Clang and GCC, choose to use bit-copy, and the result is signed -1. With this the above code works fine, both the NSInteger and the int (if you've less than 2^32-1 elements in your array) versions.

What the comments raise is how to avoid this implementation-defined behaviour. If this concerns you the following will handle the empty array case correctly with ease:

for (NSUInteger i = restArray.count; i > 0; )
{
   i--; // decrement the index
   // loop body as before
}

If the array is empty the loop test, i > 0 , will fail immediately. If the array is non-empty i , being initialised to the count , will start as one greater than the maximum index and the decrement in the loop will adjust it - effectively in the loop test i contains the number of elements left to process and in the loop body after the decrement contains the index of the next element to process.

Isn't C fun (and mathematically incorrect by definition)!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM