【问题标题】:Save a big batch of photos using the new Photos framework?使用新的照片框架保存大量照片?
【发布时间】:2014-11-28 17:59:35
【问题描述】:

我正在尝试使用 iOS 8 中的新 PHAssetChangeRequest 类将一大批照片保存到照片库中。问题是,看起来保存照片的守护进程本身意外崩溃,数量适中照片(我正在尝试大约 500 张)。任何人都知道如何绕过这个限制?这是守护进程本身的内存使用问题吗?这也可能是更改块的超时限制,因为在下面的前 2 个日志语句之间存在一个 not 微不足道的差距。

assetsd 守护进程不应该已经考虑了这个用例吗,因为新照片框架中的 超级复杂 模型和设计应该能够处理类似的事情?文档示例本身展示了保存照片的能力。

这是我的代码示例:

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
    for (NSURL * url in fileURLs) {
        PHAssetChangeRequest * assetReq = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
    }
    NSLog(@"Added %d assets",fileURLs.count);
} completionHandler:^(BOOL success, NSError *error) {
     if (!success){
         NSLog(@"%@",error);
     }
}];

这就是我的输出:

... Added 501 assets
... Connection to assetsd was interrupted or assetsd died
... Error Domain=NSCocoaErrorDomain Code=-1 "The operation couldn’t be completed. (Cocoa error -1.)

我什至尝试了PHPhotoLibrary中的同步performChangesAndWait方法,但它也有同样的问题。

我对建议/想法持开放态度,我被困住了! :(

【问题讨论】:

  • 我遇到了类似的问题。我保存了 4 张图像,它可以工作,但如果我再次尝试保存 4 张图像来替换现有图像,我会收到此错误。当我想出解决方案时,我会回复你
  • @Aggressor 我真的看不到“替换”照片库中现有照片的方法。我想你可以删除并重新添加它们。
  • 这个错误似乎来自内存过载。我减小了 UIImage 的文件大小并解决了问题。如果您需要查看,我可以发布代码。
  • 嗯,不,对减少文件大小的代码不感兴趣。我需要保存原始照片,而不是调整大小。我会想出一种不同的方法。
  • 我可以保证它的内存超出错误。您是否尝试过一次只做几个然后清除缓存?就像将其分成 10 个批次并每次清除缓存一样。你的 didRecieveMemory 函数中有 println 吗?如果您运行所有 500 个,内存功能会在多少时触发?你能在 didRecieveMemory 警告中放一个清除缓存调用吗?

标签: ios ios8 photosframework


【解决方案1】:

我们的想法是小批量生产。 PHPhotoLibrary::performChanges 一起提交一次更改请求,因此即使它能够完成,您也无法从代表或块中获取任何有关进度的更新。 2017 年 WWDC 会议 "What's New in Photos APIs" 附带了一个示例应用程序“Creating Large Photo Libraries for Testing”,它就是这样做的。每个performChanges 有10 张图片提交,UI 更新来自performChanges 的完成块,有一个信号量阻塞线程,直到处理完一批。我在这里发布重要的代码片段:

private func addPhotos() {
    let batchSize = min(photosToAdd, maxBatchSize)
    if batchSize <= 0 || !active {
        isAddingPhotos = false
        active = false
        return
    }

    workQueue.async {
        let fileURLs = self.generateImagesAndWriteToDisk(batchSize: batchSize)
        self.createPhotoLibraryAssets(with: fileURLs)

        DispatchQueue.main.async {
            self.addPhotos()
        }
    }
}

private func createPhotoLibraryAssets(with imageFileURLs: [URL]) {
    photoLibrarySemaphore.wait() // Wait for any ongoing photo library

    PHPhotoLibrary.shared().performChanges({
        let options = PHAssetResourceCreationOptions()
        options.shouldMoveFile = true
        for url in imageFileURLs {
            let creationRequest = PHAssetCreationRequest.forAsset()
            creationRequest.addResource(with: .photo, fileURL: url, options: options)
        }
    }) { (success, error) in
        if !success {
            print("Error saving asset to library:\(String(describing: error?.localizedDescription))")
        }
        self.photoLibrarySemaphore.signal()
    }
}

generateImagesAndWriteToDisk 以上是您需要替换为返回一批 10 个左右照片 URL 的方法的方法。我个人不喜欢递归写作。代码可以轻松更改为非递归样式。

【讨论】:

  • 我遇到了同样的问题,并使用了 maxConcurrentOperationCount 设置为 1 的 OperationQueue
  • 您知道如何在使用creationRequest.addResource(with: .photo, fileURL: url, options: options) 时将创建日期和位置数据添加到正在创建的资产中吗?
【解决方案2】:

我使用了这个方法而不是creationRequestForAssetFromImageAtFileURL,它非常适合10张图片(这部分代码在tableView:cellForRowAtIndexPath:中重复)

    UIImage *thisImage=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",serverInfo,self.URLs[indexPath.row]]]]];
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest * assetReq = [PHAssetChangeRequest creationRequestForAssetFromImage:thisImage];
        NSLog(@"Added %ld assets",(long)indexPath.row);
    } completionHandler:^(BOOL success, NSError *error) {
        if (!success){
             NSLog(@"%@",error);
        }
}]; 

【讨论】:

    【解决方案3】:

    在设备上处理大量图像时,即使在现代 ARC 时代,您也必须非常小心内存管理。我要处理大量图像(50+ 需要调整大小),最终选择了 CGImageSourceCreateThumbnailAtIndex() as suggested by the answer here。它使用应该非常有效的 ImageIO。我剩下的唯一问题是,由于某种原因,除非我将 for 循环包装在 @autoreleasepool {}

    中,否则它仍会保留在内存中
    for (ALAsset *asset in assets) {
            @autoreleasepool {
                resizedImage = [self thumbnailForAsset:asset maxPixelSize:JPEG_MAX_DIMENSION];
            // do stuff here
        }
    }
    

    【讨论】:

      【解决方案4】:

      这并不是这个问题的真正解决方案,但它是一种解决方法。您仍然可以使用旧的 ALAssetsLibrary 将文件成功保存到相机胶卷/照片应用。

      ALAssetsLibrary* lib = [[ALAssetsLibrary alloc] init];
      [lib writeImageDataToSavedPhotosAlbum:imageData metadata:nil
                            completionBlock:^(NSURL *assetURL, NSError *error)
      {
          // start saving your next image
      }];
      

      还建议在循环浏览要保存的所有照片时使用 AssetsLibrary 的单个实例。您可能还想等待第一个图像保存完成以开始保存下一个。

      然后,如果需要,您可以将生成的assetURL 转换为 PHAsset:

      + (PHFetchResult *)fetchAssetsWithALAssetURLs:(NSArray *)assetURLs
                                        options:(PHFetchOptions *)options
      

      【讨论】:

        猜你喜欢
        • 2015-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-03
        • 1970-01-01
        • 2015-03-10
        • 2017-08-24
        相关资源
        最近更新 更多