【问题标题】: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) 
    {
        ... //
    }];
}

【问题讨论】:

  • 重写您的代码,以便在调用第一个图像的完成块后保存第二个图像,依此类推。
  • 我尝试以递归方式编写它,但仍然收到内存警告

标签: ios objective-c alassetslibrary


【解决方案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.
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-30
    相关资源
    最近更新 更多