Reputation: 35
I'm using the following method to download images for artists in an array. I use LastFm to get the images, then store them in ESCache.
self.imageDownloadingQueue = [[NSOperationQueue alloc] init];
self.imageDownloadingQueue.maxConcurrentOperationCount = 1;
self.artworkCache = [[ESCache alloc] initWithName:@"artworkCache" error:nil];
NSArray *artists = // get artists using relevant framework;
for (MPMediaItemCollection *collection in artists){
[self.imageDownloadingQueue addOperationWithBlock:^{
@autoreleasepool {
MPMediaItem *item = [collection representativeItem];
NSString *artistString = [item valueForProperty:MPMediaItemPropertyAlbumArtist];
BOOL isCachedImage = [self.artworkCache objectExistsForKey:artistString];;
if (!isCachedImage){
[[LastFm sharedInstance] getInfoForArtist:artistString successHandler:^(NSDictionary *result) {
if ([artistString isEqualToString:[[result objectForKey:@"_params"] objectForKey:@"artist"]]) {
NSURL *imageURL = [result objectForKey:@"image"];
if (imageURL){
NSData * data = [NSData dataWithContentsOfURL:imageURL];
UIImage *artistImage = [UIImage imageWithData:data];
[self.artworkCache setObject:artistImage forKey:artistString];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[downloadButton setTitle:artistString forState:UIControlStateNormal];
[downloadButton setBackgroundImage:artistImage forState:UIControlStateNormal];
}];
artistImage = nil;
}
else{
UIImage *newArtworkImage = // get placeholder image;
if (newArtworkImage != nil){
[self.artworkCache setObject:newArtworkImage forKey:artistString];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[downloadButton setTitle:artistString forState:UIControlStateNormal];
[downloadButton setBackgroundImage:newArtworkImage forState:UIControlStateNormal];
}];
newArtworkImage = nil;
}
}
}
else{
}
} failureHandler:^(NSError *error) {
}];
}
}
}];
}
I see my memory usage increasing in Xcode to just over 200mb, then my app crashes with the following error:
2015-01-18 20:56:36.479 Mezzo[1481:39314] Received memory warning. Mezzo(1481,0x105254000) malloc: * mach_vm_map(size=147456) failed (error code=3) * error: can't allocate region *** set a breakpoint in malloc_error_break to debug
It loops through the first 50 or so artists, gets their images (I can see downloadButton
change) but then it crashes. I'm not sure how to deal with the increasing memory usage issue. I set the images to nil
after I've stored them and I put everything in an autoreleasepool
.
How else would I reduce memory usage as it loops through this array? Any help would be much appreciated. Thanks.
Upvotes: 0
Views: 221
Reputation: 437412
Any pattern that entails loading all of the images into memory at one time is likely to cause problems. Stream them directly to persistent storage. Then, as needed by the UI, load them into the image view (and to the cache).
It's unclear what the UI is (i.e. whether all of the image views are currently visible, or whether you're populating a scroll view or something like that). Only load images into memory that need to be visible at any given time. If you use collection views or table views, this is quite simple. If you're manually populating scrollviews, then you'll have to go through some work to determine which are visible, unloading them as the scroll off screen, etc.
Make sure the cache registers for UIApplicationDidReceiveMemoryWarningNotification
and empties the cache upon memory pressure. The cache should simply be used as a mechanism to optimize speed of the UI, but under memory pressure, the cache should purge itself and the UI should retrieve images from persistent storage. So the UI should see if it's in cache, if so, use that. If not, see if it's in persistent storage and if so, use that (loading it into the cache in the process). And only if it's not found in either the cache or persistent storage would it retrieve it from the network.
Are the images higher resolution than is required by the UI? E.g., if your buttons are 44 x 44 pt, and the screen scale is 2, then resize the image to 88 x 88px. If you resize them to screen resolution images, it can reduce the memory impact significantly.
By the way, once you solve these memory issues, you can then re-evaluate the maxConcurrentOperationCount
of 1
. That carries a serious performance penalty. Generally, I'd advise 4 or 5. But don't tackle this until you've solved your memory issues.
Having said that, if you're doing asynchronous downloads, I hope you know that this defeats the intent of your maxConcurrentOperationCount
of 1
. If you want to constrain the degree of concurrency, you have to wrap these operations in an asynchronous NSOperation
subclass (and implementation that does not complete the operation until the asynchronous download is done).
Upvotes: 1