Force async tasks to run in sequence

半世苍凉 提交于 2019-12-23 04:23:20

问题


I'm saving images to photo library using ALAssetsLibrary. When when it gets in loop it runs simultaneously and causing memory problems. How can I run this without causing memory problems

ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
for(UIImage *image in images){
    [library writeImageToSavedPhotosAlbum:[image CGImage] 
                              orientation:(ALAssetOrientation)[image imageOrientation]       
                          completionBlock:^(NSURL *assetURL, NSError *error) 
    {
        ... //
    }];
}

回答1:


If you want to ensure that these writes happen serially, you could use a semaphore to wait for the completion of the image before initiating the next write:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

for (UIImage *image in images) {
    [library writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error) {
        dispatch_semaphore_signal(semaphore);                  // signal when done
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for signal before continuing
}

And, since you probably don't want to block the main queue while this is going on, you might want to dispatch that whole thing to some background queue:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    for (UIImage *image in images) {
        [library writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error) {
            dispatch_semaphore_signal(semaphore);                  // signal when done
        }];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for signal before continuing
    }
});

You could, alternatively, wrap this writeImageToSavedPhotosAlbum in a custom NSOperation that doesn't post isFinished until the completion block, but that seems like overkill to me.


Having said that, I worry a little about this array of images, where you're holding all of the UIImage objects in memory at the same time. If they are large, or if you have many images, that could be problematic. Often you would want to simply maintain an array of, say, image names, and then instantiate the images one at a time, e.g.:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    for (NSString *path in imagePaths) {
        @autoreleasepool {
            ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

            UIImage *image = [UIImage imageWithContentsOfFile:path];

            [library writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error) {
                dispatch_semaphore_signal(semaphore);                  // signal when done
            }];

            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for signal before continuing
        }
    }
});

One should generally be wary of any coding pattern that presumes the holding an array of large objects, like images, in memory at the same time.

If you need to frequently access these images, but don't always want to re-retrieve it from persistent storage every time, you could employ a NSCache pattern (e.g. try to retrieve image from cache; if not found, retrieve from persistent storage and add it to the cache; upon memory pressure, empty cache), that way you enjoy the performance benefits of holding images in memory, but gracefully handle situation where the image cache consumes too much memory.




回答2:


Another approach is to implement an "asynchronous loop":

typedef void(^completion_t)(id result, NSError* error);

- (void) saveImages:(NSMutableArray*)images 
     toAssetLibrary:(ALAssetsLibrary*)library 
         completion:(completion_t)completionHandler
{
    if ([images count] > 0) {
        UIImage* image = [images firstObject];
        [images removeObjectAtIndex:0];

        [library writeImageToSavedPhotosAlbum:[image CGImage]
                                  orientation:(ALAssetOrientation)[image imageOrientation]
                              completionBlock:^(NSURL *assetURL, NSError *error)
        {
            if (error) {

            }
            else {
            }
            [self saveImages:images toAssetLibrary:library completion:completionHandler];
        }];
    }
    else {
        // finished
        if (completionHandler) {
            completionHandler(@"Images saved", nil);
        }
    }
}

Notice:

  • The method saveImages:toAssetLibrary:completion: is an asynchronous method.

  • It sequentially processes a list of images.

  • The completion handler will be called when all images have been saved.

In order to accomplish this, the implementation above invokes itself in the completion handler of writeImageToSavedPhotosAlbum:orientation:completionBlock:.

This is NOT a recursive method invocation, though: when the completion handler invokes the method saveImages:toAssetLibrary:completion:, the method already returned.

Possible improvements:

  • For brevity, the sample has no error handling. This should be improved in a real implementation.

  • Instead of having a list of images, you'd better off using a list of URLs to the images.

Usage

You may use it like this:

ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];   
[self saveImages:[self.images mutableCopy] toAssetLibrary:library 
completion:^(id result, NSError*error) {
    ... 
}];



回答3:


If you want the update to use dispatch_async instead.

// This will wait to finish
dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
});


来源:https://stackoverflow.com/questions/20259733/force-async-tasks-to-run-in-sequence

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!