问题
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