为什么我的NSMutableArray在某些索引上包含nil?

The*_*ter 6 objective-c nsmutablearray nsarray ios mapbox

问题:

在我填充了一个NSMutableArray对象并尝试用它做某事之后,它包含(id)0x0了一些索引.我认为NSMutableArray在Objective-C中根本不可能添加nil 所以我想知道为什么会发生这种情况.

这是什么时候发生的?

不幸的是,"有时候".它可以通过下载超过5000个瓷砖来重现,只是为了获得足够高的金额以便发生这种情况.即使有超过5000个瓷砖,它有时也会完美无缺.

语境:

我的应用程序有一个按钮,可以开始下载特定区域的地图图块.下载在后台线程中并行发生,并为每个下载的磁贴报告.

为了允许取消下载,在我的下载器单例中,我有一个临时表NSMutableArray,可以保存每个下载的磁贴的哈希值.取消后,我可以使用该哈希列表删除数据库中的每个已保存的磁贴.

在下载过程中保存哈希似乎没问题,但是当我真的想要对它做任何事情时(我用[_currentTileHashes copy]它来改变它NSArray以给予删除方法),它会抛出NSInvalidArgumentException一行说:

-[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3402]
Run Code Online (Sandbox Code Playgroud)

当我使用调试器检查_currentTileHashes可变数组时,我确实看到一个或两个索引实际上是nil或(id)0x0.这个截图说明了它:

调试器在数组中显示nil

相关代码:

此代码来自每个tile下载的回调,它会散列磁贴,将其添加到hashes数组并回调UI以获取进度:

- (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];
    }
}
Run Code Online (Sandbox Code Playgroud)

这是瓦片被散列的方式(这是来自Mapbox iOS SDK):

+ (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;
}
Run Code Online (Sandbox Code Playgroud)

最后,发生异常的代码:

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

    [tileCache removeAllCachedImagesForTileHashes:[_currentTileHashes copy]];

    [[NSNotificationCenter defaultCenter]
     postNotificationName:@"routeTileDownloadCanceled"
     object:nil];
}
Run Code Online (Sandbox Code Playgroud)

在iOS 8.4,8.4.1(iPhone 6)和7.1(iPhone 4)上测试

如果不清楚,请随时要求更多澄清.

Pau*_*w11 4

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];
    }
}
Run Code Online (Sandbox Code Playgroud)