强制异步任务按顺序运行
Posted
技术标签:
【中文标题】强制异步任务按顺序运行【英文标题】:Force async tasks to run in sequence 【发布时间】:2013-11-28 07:05:05 【问题描述】:我正在使用ALAssetsLibrary
将图像保存到照片库。当它进入循环时,它会同时运行并导致内存问题。如何在不导致内存问题的情况下运行它
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
for(UIImage *image in images)
[library writeImageToSavedPhotosAlbum:[image CGImage]
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error)
... //
];
【问题讨论】:
重写您的代码,以便在调用第一个图像的完成块后保存第二个图像,依此类推。 我尝试以递归方式编写它,但仍然收到内存警告 【参考方案1】:如果您想确保这些写入连续发生,您可以使用信号量等待图像完成,然后再开始下一次写入:
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
而且,由于您可能不想在此过程中阻塞主队列,因此您可能希望将整个事情分派到某个后台队列:
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
);
您也可以将这个writeImageToSavedPhotosAlbum
包装在一个自定义的NSOperation
中,直到完成块才发布isFinished
,但这对我来说似乎有点矫枉过正。
话虽如此,我还是有点担心images
的这个数组,你在内存中同时保存了所有UIImage
对象。如果它们很大,或者如果你有很多图像,那可能会有问题。通常你会想要简单地维护一组图像名称,然后一次实例化一个图像,例如:
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
);
人们通常应该警惕任何假定同时在内存中保存一组大型对象(如图像)的编码模式。
如果您需要经常访问这些图像,但又不想每次都从持久存储中重新检索,您可以使用NSCache
模式(例如,尝试从缓存中检索图像;如果找不到,从持久存储中检索并将其添加到缓存中;在内存压力下,清空缓存),这样您就可以享受将图像保存在内存中的性能优势,但可以优雅地处理图像缓存消耗过多内存的情况。
【讨论】:
谢谢你,我认为我的代码在你的帮助下得到了改进。但是保存3张图片后它仍然会关闭 它关闭并在 Xcode 中生成错误消息说“由于内存压力而终止”。 BTW 刚刚发现同时调用library writeImageToSavedPhotosAlbum
会在每次调用后导致更高的内存峰值,无论调用的时间间隔如何。
当 Xcode 记录“因内存压力而终止”时,您的应用程序是否在后台?
@DulguunLst 然后您可以通过将库的实例化移动到自动释放池中来解决这个问题。这可能会对性能造成适度的影响(为每个图像重新实例化它),但应该可以解决内存问题。请参阅修改后的答案(最后一段代码)。
尝试将实例化放入自动释放池并在每次完成后将其设置为 Nil 并在下一个图像上重新创建它。结果同样开始怀疑这是 xcode 或 ios 错误。或者我错过了一些清除一些未知内容的电话。现在真的很困惑。【参考方案2】:
另一种方法是实现“异步循环”:
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);
注意:
方法saveImages:toAssetLibrary:completion:
是一个异步方法。
它按顺序处理图像列表。
当所有图像都保存后,将调用完成处理程序。
为了完成这个,上面的实现在writeImageToSavedPhotosAlbum:orientation:completionBlock:
的完成处理程序中调用自己。
不过,这不是递归方法调用:当完成处理程序调用方法 saveImages:toAssetLibrary:completion:
时,该方法已经返回。
可能的改进:
为简洁起见,示例没有错误处理。这应该在实际实现中得到改进。
最好不要使用图像列表,而是使用图像的 URL 列表。
用法
你可以这样使用它:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[self saveImages:[self.images mutableCopy] toAssetLibrary:library
completion:^(id result, NSError*error)
...
];
【讨论】:
我实现了类似的东西,但没有完成处理程序,它仍然导致内存警告。并尝试了你的,仍然引起内存警告。我认为这是因为这个 ALAssetsLibrary。这是多么令人讨厌的事情。 也许使用 URL 列表而不是图像列表? (参见 Rob 的第二个建议) 是的,我使用 URL 列表作为 NSStrings。但我想我可能没有发现什么改进。尝试让方法创建它自己的 ALAssetsLibrary 并将其设置为Nil
,然后再次调用自身。应用程序收到内存警告但有时没有关闭。
BTW 刚刚发现同时调用 library writeImageToSavedPhotosAlbum
会在每次调用后导致更高的内存峰值,无论调用的时间间隔如何。
@DulguunLst 确保,writeImageToSavedPhotosAlbum:..
将一个接一个地被调用。确保在写入资产库之前使用 URL 并加载图像。完成对库的写入后,将图像设置为零。除此之外,使用自动释放池来包装所有这些方法,以确保释放临时对象。如果这没有帮助,您需要更深入地挖掘并尝试使用 Instruments 找出内存消耗的原因。【参考方案3】:
如果您希望更新使用 dispatch_async 代替。
// This will wait to finish
dispatch_async(dispatch_get_main_queue(), ^
// Update the UI on the main thread.
);
【讨论】:
以上是关于强制异步任务按顺序运行的主要内容,如果未能解决你的问题,请参考以下文章