Безопасность NSMutableArray

В моем приложении я просматриваю и изменяю измененный массив из нескольких потоков. Сначала он разбивался, когда я пытался получить доступ к объекту с помощью objectAtIndex, потому что индекс был за пределами (объект в этом индексе уже удален из массива в другом потоке). Я искал в Интернете, как решить эту проблему, и решил попробовать это решение. Я создал класс с свойством NSMutableArray, см. Следующий код:

@interface SynchronizedArray()
@property (retain, atomic) NSMutableArray *array;
@end

@implementation SynchronizedArray

- (id)init
{
    self = [super init];
    if (self)
    {
        _array = [[NSMutableArray alloc] init];
    }
    return self;
}

-(id)objectAtIndex:(NSUInteger)index
{
    @synchronized(_array)
    {
        return [_array objectAtIndex:index];
    }
}

-(void)removeObject:(id)object
{
    @synchronized(_array)
    {
        [_array removeObject:object];
    }
}

-(void)removeObjectAtIndex:(NSUInteger)index
{
    @synchronized(_array)
    {
        [_array removeObjectAtIndex:index];
    }
}

-(void)addObject:(id)object
{
    @synchronized(_array)
    {
        [_array addObject:object];
    }
}

- (NSUInteger)count
{
    @synchronized(_array)
    {
        return [_array count];
    }
}

-(void)removeAllObjects
{
    @synchronized(_array)
    {
        [_array removeAllObjects];
    }
}

-(id)copy
{
    @synchronized(_array)
    {
        return [_array copy];
    }
}

и я использую этот класс вместо старого измененного массива, но приложение все еще терпит крах в этой строке: return [_array objectAtIndex:index]; Я тоже пробовал этот подход с NSLock, но без везения. Что я делаю неправильно и как это исправить?

Ответ 1

Я считаю, что это решение плохое. Рассмотрим это:

  • thread # 1 вызывает count и сказано, что в массиве 4 объекта. Массив
  • несинхронизирован.
  • thread # 2 вызывает removeObjectAtIndex:2 в массиве. Массив
  • несинхронизирован.
  • thread # 1 вызывает objectAtIndex:3 и возникает ошибка.

Вместо этого вам нужен механизм блокировки на более высоком уровне, где блокировка находится вокруг массива на обоих этапах 1 и 5, а поток # 2 не может удалить объект между этими шагами.

Ответ 2

Вам необходимо защитить (с @synchronized) в основном все использование массива. В настоящее время вы предотвращаете одновременное получение нескольких потоков из массива. Но у вас нет защиты для описанного вами сценария параллельной модификации и мутации.

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