NSMutable Array - objectAtIndex: индекс за пределами - PullRequest
2 голосов
/ 02 ноября 2009

Я пытаюсь просмотреть NSMutableDictionary, загруженный с NSMutableArray, и я испортил его, и я не знаю как. Я пытаюсь загрузить большой список игровых вопросов, а затем удалить их, если они не на нужном уровне. Я не получаю сообщение об ошибке, пока не попытаюсь удалить. Кто-нибудь может увидеть недостаток в этом коде? Я очень ценю это!

Спасибо

~ G

{
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
NSString *GameLevel = [[NSString alloc] initWithFormat: [settings objectForKey:kLevelKey]];

NSBundle *Bundle = [NSBundle mainBundle];
NSString *PListPath = [Bundle pathForResource:@"questions" ofType:@"plist"];

NSMutableDictionary *Dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:PListPath]; 

self.QuestionDetailsByLevel = Dictionary;
[Dictionary release];

NSMutableArray *Components = [[NSMutableArray alloc] initWithArray:[QuestionDetailsByLevel allKeys]];
self.QuestionsByLevel = Components;

int QuestionCount = [self.QuestionsByLevel count] - 1;

for (int j = 0; j < QuestionCount - 1; j++)
{

    NSString *SelectedQuestion = [self.QuestionsByLevel objectAtIndex:j];
    NSMutableArray *Array = [QuestionDetailsByLevel objectForKey:SelectedQuestion];
    self.QDetailsByLevel = Array;

    NSString *level = [[NSString alloc] initWithFormat:[self.QDetailsByLevel objectAtIndex:Level]];

    if (level != GameLevel)
        [QuestionsByLevel removeObjectAtIndex:j];   
}
}

Ответы [ 3 ]

10 голосов
/ 02 ноября 2009

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

Вот соответствующий код.

for (int j = 0; j < QuestionCount - 1; j++)
{

    NSString *SelectedQuestion = [self.QuestionsByLevel objectAtIndex:j];
    // ... snip ...
    if (level != GameLevel) //Always happening in your current code
        [QuestionsByLevel removeObjectAtIndex:j];       
}

Посмотрим, что произойдет после нескольких итераций этого кода.

Первая итерация:

j == 0
self.QuestionsByLevel == [Q1, Q2, Q3, Q4, Q5]

SelectedQuestion = QuestionsByLevel[0] // Q1

// The following happens because you call removeObjectAtIndex:0
QuestionsByLevel = [Q2, Q3, Q4, Q5]

Вторая итерация:

j == 1
self.QuestionsByLevel == [Q2, Q3, Q4, Q5]
SelectedQuestion = QuestionsByLevel[1] // Q3

// The following happens because you call removeObjectAtIndex:1
QuestionsByLevel = [Q2, Q4, Q5]

Третья итерация:

j == 2
self.QuestionsByLevel == [Q2, Q4, Q5]
SelectedQuestion = QuestionsByLevel[2] // Q5

// The following happens because you call removeObjectAtIndex:2
QuestionsByLevel = [Q2, Q4]

Четвертая итерация:

j == 3
self.QuestionsByLevel == [Q2, Q4]
SelectedQuestion = QuestionsByLevel[3] // CRASH!!!! 

Вы видите проблему? Ваш цикл for предполагает, что вы будете обращаться к объектам по их индексу, но затем после каждой итерации вы удаляете что-то из массива, что сдвигает все индексы после этой точки. Вам не следует звонить removeObjectAtIndex:, потому что вы пытаетесь пройти по массиву одновременно.

Если вы просто пытаетесь пропустить определенный объект, вы можете просто вызвать «продолжить», когда достигнете этого объекта. Если вы действительно хотите удалить его из массива, просто позвоните [QuestionsByLevel removeObject:GameLevel]. Или то, что имеет смысл для вашей ситуации. Но сделайте это за до , пока не выполните итерацию по массиву.

9 голосов
/ 02 ноября 2009

Это не ответ. Это критика вашего кода.

  1. Святая утечка памяти, Бэтмен! Вы выделяете / init: GameLevel, Components и level, но никогда не освобождаете ни одного из них.
  2. GameLevel вообще не нужно выделять / инициализировать. Вы можете просто извлечь значение из [settings objectForKey:kLevelKey];, назначить его в строку GameLevel и использовать его. Тогда вам даже не нужно выпускать его.
  3. Ваш цикл ... странный. Вы повторяете цикл, но каждый раз, когда вы повторяете, вы устанавливаете для свойства self.QDetailsByLevel новое значение. Вы уверены, что это то, что вы хотите?
  4. Это: if (level != GameLevel) не делает то, что вы думаете, что делает. Это сравнение указателей (то есть АДРЕСА двух объектов в памяти). В вашем текущем состоянии level и GameLevel были выделены / инициализированы, что означает, что они никогда не будут одним и тем же объектом. Вы, вероятно, хотите вместо if ([level isEqualToString:GameLevel] == NO).
  5. Вы вычитаете одно из [self.QuestionsByLevel count], чтобы получить QuestionCount int, который будет выглядеть как верхняя граница цикла for (). Тем не менее, условие для цикла for (который, как показал @Michael, является вашей проблемой) вычитает другой 1 из QuestionCount, что означает, что цикл for () никогда не достигнет последнего элемента в массиве. Вы уверены, что это то, что вы хотите?
  6. Запомните это: http://www.cocoadevcentral.com/articles/000082.php (или это )
3 голосов
/ 02 ноября 2009

Проблема возникает, если я не ошибаюсь, из-за того, что вы изменяете объект, пока вы перебираете его содержимое. Когда вы удаляете объекты из списка, ваше условие завершения становится недействительным. Попробуйте реализовать это как цикл while, и будьте осторожны, чтобы обновлять условие завершения при каждом удалении элементов из списка. Вы также можете использовать отладчик, чтобы узнать, где вы были за пределами.

...