Search code examples
iosobjective-csynchronizationalassetslibrary

Synchronization issue with ALAssetsLibrary assetForURL method


I am having some synchronization issue with loading asset from ALAssetsLibrary.

Actually, what I am trying is to load some pictures from camera roll whose urls are given by some database query. Now after obtaining urls from database I use those urls to load the pictures using assetForURL method of ALAssetsLibrary and after the picture is loaded I display the picture to some view. So I call the method inside a loop that is executed every time the query result-set returns a record. And everything works fine till now. Below is a sample code to demonstrate the process:

ALAssetsLibrary* library = [ALAssetsLibrary new];
//dispatch_group_t queueGroup = dispatch_group_create();
while ([rs next]) {
    //some data load up

    //load thumbnails of available images
    [library assetForURL:url resultBlock:^(ALAsset *asset) {
        UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];

        //dispatch_group_async(queueGroup, dispatch_get_main_queue(), ^{
            //create views and add to container view
            CGFloat left = (8.0f + dimension) * i + 8.0f;
            CGRect rect = CGRectMake(left, 8.0f, dimension, dimension);
            TileView* tileView = [[NSBundle mainBundle] loadNibNamed:@"TileView" owner:nil options:nil][0];
            [tileView setFrame:rect];
            tileView.tag = i;
            tileView.active = NO;
            [self.thumbnailContainer addSubview:tileView];

                            //display image in tileView, etc.

                            //.............
            if (img) {
                [img release];
            }
            NSLog(@"block %d: %d",i,[self.thumbnailContainer.subviews count]);
            //});
    } failureBlock:^(NSError *error) {
        NSLog(@"failed to load image");
    }];

    i++;
}

NSLog(@"outside block %d",[self.thumbnailContainer.subviews count]);
[library release];

In my code self.thumbnailContainer is a UIScrollView and inside that I add my custom views to display the thumbnail images and it works as expected.

The real dilemma comes when I try to select the very last view added to self.thumbnailContainer. I cant find any way to determine when all the asynchronous blocks of assetForURL methods completed so that self.thumbnailContainer actually contains some subviews. So if I log count of subviews of self.thumbnailContainer just after the loop completes it shows 0. And after that I find all the block codes get executed increasing count of subviews. It is very expected behavior but contradicts my requirements. I have tried dispatch_group_ and dispatch_wait methods from GCD but without any success.

Can anyone please suggest a workaround or an alternative coding pattern to overcome the situation. Any help would be highly appreciated. Thanks.


Solution

  • You might utilize a dispatch group, as you likely had in mind:

    - (void) loadViewsWithCompletion:(completion_t)completionHandler {
        ALAssetsLibrary* library = [ALAssetsLibrary new];
        dispatch_group_t group = dispatch_group_create();
        while ([rs next]) {
            dispatch_group_enter(group);
            [library assetForURL:url resultBlock:^(ALAsset *asset) {
                UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
    
                dispatch_async(dispatch_get_main_queue(), ^{
                    //create views and add to container view
                    ...
    
                    dispatch_group_leave(group);
                });
            } failureBlock:^(NSError *error) {
                NSLog(@"failed to load image");
                dispatch_group_leave(group);
            }];
    
            i++;
        }
        [library release];
        if (completionHandler) {
            dispatch_group_notify(group, ^{
                completionHandler(someResult);
            });
        }
        ...  release dispatch group if not ARC
    }
    

    The code might have a potential issue though:

    Since you asynchronously load images, they may be loaded all in parallel. This might consume a lot of system resources. If this is the case, which depends on the implementation of the asset loader method assetForURL:resultBlock:failureBlock:, you need to serialize your loop.

    Note: Method assetForURL:resultBlock:failureBlock: may already ensure that access to the asset library is serialized. If the execution context of the completion block is also a serial queue, [UIImage imageWithCGImage:asset.aspectRatioThumbnail] will be executed in serial. In this case, your loop just enqueues a number of tasks - but processes only one image at a time and you are safe.

    Otherwise, if method assetForURL:resultBlock:failureBlock: runs in parallel, and/or the block executes an a concurrent queue - images might be loaded and processed in parallel. This can be a bad thing if those images are large.