Необходимо изменить NSMutableArray, предварительно загруженный данными во время быстрого перечисления в iOS.

У меня есть NSMutableArray, который содержит список объектов. То, что я пытаюсь сделать, это перебрать этот список объектов и найти соответствующий объект для того, что я пытаюсь вставить. Как только я найду соответствующий объект, я хочу просто заменить объект, который в настоящее время находится в списке, на тот, который я пытаюсь вставить. Я пытаюсь сделать это с помощью быстрого перечисления:

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

        }

    }

К сожалению, когда я запускаю приведенный выше код, я получаю следующую ошибку:

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

Может ли кто-нибудь увидеть, что я делаю неправильно, и как это обойти, но при этом достичь функциональности, описанной выше?


person syedfa    schedule 18.12.2012    source источник


Ответы (4)


Во-первых, авария довольно понятна. На самом деле вы мутируете (то есть заменяете объект в массиве), в то время как быстрое перечисление все еще выполняется, что не разрешено.

Решение, если вы пойдете со своим дизайном, будет состоять в том, чтобы фактически захватить индекс объекта, разорвать быстрое перечисление и заменить объект вне быстрого перечисления.

Однако то, что вы делаете, не правильно. Способ использования indexOfObjectPassingTest таков:

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

}

Вам не нужно вручную перечислять все элементы массива. Функция делает это за вас внутри.

person Lefteris    schedule 18.12.2012
comment
Большое спасибо, что показали мне, как это сделать. Я чувствую себя еще более невежественным, чем раньше :-) - person syedfa; 19.12.2012
comment
@Лефтерис, +1. Ты прав. Это перечисление было излишним. - person iDev; 19.12.2012
comment
@syedfa Мы все еще учимся у более опытных разработчиков. Спрашивать — это неплохо, как и учиться на своих ошибках! - person Lefteris; 19.12.2012
comment
@ACB Это привлекло мое внимание, когда я задался вопросом, почему syedfa делает это, поэтому я сразу это заметил. - person Lefteris; 19.12.2012
comment
Еще раз спасибо Лефтерис. Заботиться. - person syedfa; 19.12.2012

Как говорится в сообщении об ошибке, вы не можете изменить массив, используемый в букле foreach. Измените свой код на это:

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

NSInteger indx = NSNotFound;
for (TestResult *checkTest in [DataModel sharedInstance].testResultList) {

    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];                            
}
person tkanzakic    schedule 18.12.2012

Вы не можете заменять, удалять или вставлять элементы в массив во время перечисления. Но вы можете изменить все атрибуты элемента в idxn на атрибуты result. Таким образом, объекты остаются.

Надеюсь это поможет!

person Levi    schedule 18.12.2012

Как говорится в сообщении об ошибке, вы не можете редактировать перечисляемый массив. Вам нужно иметь другой массив, и тогда вы можете сделать это.

Следующее сработало бы,

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

NSArray *array = [NSArray arrayWithArray:[DataModel sharedInstance].testResultList];    

for (TestResult *checkTest in array) {

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

Обновление: Согласно комментарию Лефтерис, вам не нужно перебирать массив, если вы используете метод indexOfObjectPassingTest.

Вы можете просто использовать,

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

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];
}
person iDev    schedule 18.12.2012
comment
Я удивлен, что вы не замечаете очевидную ошибку и выполняете быстрое перечисление поверх метода indexOfObjectsPassing test NSArray. - person Lefteris; 19.12.2012
comment
@Lefteris, да, ты прав. Я на самом деле не думал по логике. Я был больше сосредоточен на проблеме, упомянутой ОП. - person iDev; 19.12.2012