iosnsmutablearrayenumerationfast-enumeration

Need to modify an NSMutableArray that is pre-loaded with data during fast enumeration in iOS


I have an NSMutableArray that is holding a list of objects. What I am trying to do is iterate through this list of objects, and find the matching object for the I am trying to insert. Once I find the matching object, I then want to simply replace the object that is currently in the list with the one I am trying to insert. I am trying to do this using fast enumeration:

TestResult *result = [[TestResult alloc] init];
    [result setName:name];
    [result setScore:score];
    [result setDateStamp:date];


    for (TestResult *checkTest in [DataModel sharedInstance].testResultList) {

        NSInteger indx = [[DataModel sharedInstance].testResultList indexOfObjectPassingTest:^BOOL(TestResult *obj, NSUInteger idx, BOOL *stop) {
            return [obj.name isEqualToString:name];
        }];

        if (indx != NSNotFound) {

            [[DataModel sharedInstance].testResultList replaceObjectAtIndex:indx withObject:result];

        }

    }

Unfortunately, when I run the above code, I am getting the following error:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x9624820> was mutated while being enumerated.'

Can anyone see what I am doing wrong, and how can work around this, yet achieve the functionality that I described above?


Solution

  • First of all the crash is pretty self explanatory. You are actually mutating (aka. replacing an object in the array), while the fast enumeration is still in progress, which is not permitted.

    The solution if you would go with your design would be to actually capture the index of the object, break the fast enumeration and replace the object outside the fast enumeration.

    However what you are doing is not correct. The way to use the indexOfObjectPassingTest is this:

    NSInteger indx = [[DataModel sharedInstance].testResultList indexOfObjectPassingTest:^BOOL(TestResult *obj, NSUInteger idx, BOOL *stop) {
        return [obj.name isEqualToString:name];
    }];
    
    if (indx != NSNotFound) {
    
        [[DataModel sharedInstance].testResultList replaceObjectAtIndex:indx withObject:result];
    
    }
    

    You don't need to manually enumerate through all elements of the array. The function does this for you internally.