Почему мой NSMutableArray содержит ноль по некоторым индексам?

98
6

Проблема:

После того как я заполнил объект NSMutableArray и попытался что-то сделать с ним, он содержит (id)0x0 для некоторых индексов. Я думал, что добавление nil к NSMutableArray вообще не возможно в Objective-C, поэтому мне интересно, почему это происходит.


Когда это происходит?


"Иногда", к сожалению. Он воспроизводится, загружая более ~ 5000 плиток, чтобы получить достаточно высокую сумму, чтобы получить шанс на это. Даже с более чем 5000 плиток иногда идет безупречно.


Контекст:


В моем приложении есть кнопка, которая запускает загрузку для фрагментов карты для определенного региона. Загрузка происходит параллельно в фоновом потоке, и отчеты возвращаются для каждого загружаемого фрагмента.


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


Сохранение хэшей во время загрузки кажется прекрасным, но когда я действительно хочу что-либо сделать с ним (я использую [_currentTileHashes copy], чтобы изменить его на NSArray, чтобы дать методу удаления), он выбрасывает NSInvalidArgumentException в этой строке:


-[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3402]

Когда я использую отладчик для проверки изменяемого массива _currentTileHashes, я действительно вижу, что один или два из индексов фактически равны нулю или (id)0x0. Этот снимок экрана иллюстрирует это:


Отладчик показывает nil в массиве


Соответствующий код:


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


- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(NSUInteger)tileIndex ofTotalTileCount:(NSUInteger)totalTileCount {
DebugLog(@"Cached tile %lu of %lu.", (unsigned long)tileIndex, (unsigned long)totalTileCount);
if (_currentlyDownloading) {
float progress = (float)tileIndex / (float)totalTileCount;

NSDictionary *progressDict = @{@"progress" : [NSNumber numberWithFloat:progress],
@"routeId" : _downloadingRoute.routeId};

[_currentTileHashes addObject:[RMTileCache tileHash:tile]];

[[NSNotificationCenter defaultCenter]
postNotificationName:@"routeTileDownloaded"
object:progressDict];
}
}


Так стирается хэширование (это от SDK Mapbox iOS):


+ (NSNumber *)tileHash:(RMTile)tile
{
return [NSNumber numberWithUnsignedLongLong:RMTileKey(tile)];
}

uint64_t RMTileKey(RMTile tile)
{
uint64_t zoom = (uint64_t)tile.zoom & 0xFFLL; // 8bits, 256 levels
uint64_t x = (uint64_t)tile.x & 0xFFFFFFFLL; // 28 bits
uint64_t y = (uint64_t)tile.y & 0xFFFFFFFLL; // 28 bits

uint64_t key = (zoom << 56) | (x << 28) | (y << 0);

return key;
}


И, наконец, код, в котором происходит исключение:


- (void)tileCacheDidCancelBackgroundCache:(RMTileCache *)tileCache {
DebugLog(@"Finished canceling tile download");

[tileCache removeAllCachedImagesForTileHashes:[_currentTileHashes copy]];

[[NSNotificationCenter defaultCenter]
postNotificationName:@"routeTileDownloadCanceled"
object:nil];
}


Протестировано на iOS 8.4, 8.4.1 (iPhone 6) и 7.1 (iPhone 4)


Не стесняйтесь просить больше разъяснений, если что-то неясно.

спросил(а) 2015-08-14T14:31:00+03:00 5 лет, 2 месяца назад
1
Решение
90

NSMutableArray не является потокобезопасным, поэтому обновление экземпляра из нескольких одновременных фоновых загрузок может привести к повреждению вашего массива - как вы видите.


Я бы предложил использовать @synchronized для защиты массива при его обновлении -

- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(NSUInteger)tileIndex ofTotalTileCount:(NSUInteger)totalTileCount {
DebugLog(@"Cached tile %lu of %lu.", (unsigned long)tileIndex, (unsigned long)totalTileCount);
if (_currentlyDownloading) {
float progress = (float)tileIndex / (float)totalTileCount;

NSDictionary *progressDict = @{@"progress" : [NSNumber numberWithFloat:progress],
@"routeId" : _downloadingRoute.routeId};
@synchronized(_currentTileHashes) {
[_currentTileHashes addObject:[RMTileCache tileHash:tile]];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:@"routeTileDownloaded"
object:progressDict];
}
}

ответил(а) 2015-08-14T15:48:00+03:00 5 лет, 2 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема