After I fill an NSMutableArray
with objects and try to do something with it, it contains (id)0x0
on some indices. I thought adding nil to an NSMutableArray
wasn't possible at all in Objective-C so I am wondering why this happens.
'Sometimes' unfortunately. It is reproducible by downloading more than ~5000 tiles, just to get the amount high enough for a chance for this to occur. Even with more than 5000 tiles it sometimes goes flawlessly.
My app has a button which starts a download for map tiles for a specific region. The download happens parallel in background threads and reports back for every tile downloaded.
To allow for canceling the download, in my downloader singleton I have a temporary NSMutableArray
which saves a hash from every tile downloaded. After canceling, I can use that list of hashes to delete every saved tile in the database.
Saving the hashes during downloading seems to go fine, but when I actually want to do anything with it (I use [_currentTileHashes copy]
to change it to an NSArray
to give to the delete method), it throws an NSInvalidArgumentException
on that line saying:
-[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3402]
When I use the debugger to inspect the _currentTileHashes
mutable array, I indeed see that one or two of the indices is actually nil or (id)0x0
. This screenshot illustrates it:
This code is from the callback for every tile download where it hashes the tile, adds it to the hashes array and calls back to the UI for progress:
- (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];
}
}
This is the way the tile gets hashed (this is from the 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;
}
And finally, the code where the exception occurs:
- (void)tileCacheDidCancelBackgroundCache:(RMTileCache *)tileCache {
DebugLog(@"Finished canceling tile download");
[tileCache removeAllCachedImagesForTileHashes:[_currentTileHashes copy]];
[[NSNotificationCenter defaultCenter]
postNotificationName:@"routeTileDownloadCanceled"
object:nil];
}
Tested on iOS 8.4, 8.4.1 (iPhone 6) and 7.1 (iPhone 4)
Feel free to ask for more clarification if something is unclear.
NSMutableArray
is not thread safe, so updating an instance from multiple, concurrent, background downloads is likely to lead to corruption in your array - as you are seeing.
I would suggest using @synchronized to guard the array when you update it -
- (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];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With