Question

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.

Was it helpful?

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.

OTHER TIPS

Two ways you can fix this issue. They are

1.NSLock. Specifically NSConditionLock. Basically you create a lock with the condition “pending tasks”. Then in the completion and error blocks of assetForURL, you unlock it with the “all complete” condition. With this in place, after you call assetForURL, simply call lockWhenCondition using your “all complete” identifier, and you’re done (it will wait until the blocks set that condition)!

Check this for more detials

2.Manually track the count in resultBlock & failureBlock

Note:

I have mentioned few important things regarding the performance of ALAsset libraries in my answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top