iosobjective-cafnetworking-3

iOS MAKImageGalleryView downloading photos using AFNetworking/AFImageDownloader


I am trying to do photo gallery based on MAKImageGalleryView, but I need download images from server, so I use AFImageDownloader (AFNetworking).

By the documentation of MAKImageGalleryView I have to implement this method:

MAKImageGalleryView.h

- (void)loadImageInGallery:(MAKImageGalleryView *)galleryView atIndex:(NSInteger)index callback:(void(^)(UIImage *))callback;

MAKImageGalleryView.m

#import "MAKImageGalleryView.h"

- (void)loadImageForRow:(NSInteger)row withBlockToCell:(MAKImageGalleryViewImageCell *)cell {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSUInteger key = random();
        cell.blockLoadingId = key;
        [self.imageGalleryDataSource loadImageInGallery:self atIndex:row callback:^(UIImage *image) {
            if (cell.blockLoadingId == key) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (cell.blockLoadingId == key) { //if still not reused
                        cell.image = image;
                    }
                });
            }
        }];
    });
}

So I create something like that:

ViewController.m

- (void)loadImageInGallery:(MAKImageGalleryView *)galleryView atIndex:(NSInteger)index callback:(void(^)(UIImage *))callback{

    NSString * imageName = [NSString stringWithFormat:@"http://example.com/image%i.jpg", index];

    AFImageDownloader* download = [[AFImageDownloader alloc] init];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: imageUrl] cachePolicy:NSURLRequestReturnCacheDataElseLoad
        timeoutInterval:60];
    [download downloadImageForURLRequest:request success:^(NSURLRequest * request, NSHTTPURLResponse * response, UIImage * image) {
        NSLog(@"Success download image");
        callback(image);
    } failure:^(NSURLRequest * request, NSHTTPURLResponse * response, NSError * error) {
        NSLog(@"Error download image");
    }];

Result: Time to time I see some photos, and don't see others.


Solution

  • As debugged by using comments it seems that AFImageDownloader needs to be retained until the operation is complete. If not then there is a persistent chance the downloader will be released too soon and terminate its download automatically.

    You need to retain your downloader. The most straight forward solution is to use a strong property and modify your call to:

    - (void)loadImageInGallery:(MAKImageGalleryView *)galleryView atIndex:(NSInteger)index callback:(void(^)(UIImage *))callback{
    
        NSString * imageName = [NSString stringWithFormat:@"http://example.com/image%i.jpg", index];
    
        AFImageDownloader* download = [[AFImageDownloader alloc] init];
        self.currentDownloader = download;
    
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: imageUrl] cachePolicy:NSURLRequestReturnCacheDataElseLoad
            timeoutInterval:60];
        ...
    

    If you have multiple downloaders then you will need to add them into array but also remove them:

    - (void)loadImageInGallery:(MAKImageGalleryView *)galleryView atIndex:(NSInteger)index callback:(void(^)(UIImage *))callback{
    
        NSString * imageName = [NSString stringWithFormat:@"http://example.com/image%i.jpg", index];
    
        AFImageDownloader* download = [[AFImageDownloader alloc] init];
        [self.currentDownloaders addObject: download];
    
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: imageUrl] cachePolicy:NSURLRequestReturnCacheDataElseLoad
            timeoutInterval:60];
        [download downloadImageForURLRequest:request success:^(NSURLRequest * request, NSHTTPURLResponse * response, UIImage * image) {
            NSLog(@"Success download image");
            [self.currentDownloaders removeObject: download];
            callback(image);
        } failure:^(NSURLRequest * request, NSHTTPURLResponse * response, NSError * error) {
            NSLog(@"Error download image");
            [self.currentDownloaders removeObject: download];
        }];
    

    Or as we did it with test you could simply let the block retain the download:

    By simply calling NSLog(@"Success download image %@", download); inside any of the blocks will force the block to retain download. And as long as the block is retained so will the download be. That is at least until one of the blocks has been called.

    This is the cleanest procedure but since this object is not your creation you need to check if these objects do get deallocated at some point. If they are not done correctly then this code will produce a retain cycle and with it a memory leak.

    So before you use this last procedure please check the following:

    Subclass AFImageDownloader, override dealloc and print something like NSLog(@"Did deallocate"); and use this subclass instead of your AFImageDownloader. If you see a log "Did deallocate" at some point then it is safe to use the "log trick". If not then you should not use this procedure as it will produce memory leaks!!!