Save a big batch of photos using the new Photos framework? Save a big batch of photos using the new Photos framework? ios ios

Save a big batch of photos using the new Photos framework?


The idea is doing it in small batches. PHPhotoLibrary::performChanges submits change requests once all together, so even it was able to finish, you won't be able to get any updates on the progress from a delegate or block. The 2017 WWDC session "What's New in Photos APIs" came with a sample app "Creating Large Photo Libraries for Testing" that does exactly that. Each performChanges has 10 images submitted, and UI updates are from the completion block of the performChanges, with a Semaphore blocking the thread until one batch is processed. I'm posting the important code pieces here:

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()    }}

Above generateImagesAndWriteToDisk is the method you need to replace with what ever your method to return a batch of 10 photo urls or so. I personally don't like writing in recursion. The code can be easily changed to non-recursion style.


instead of creationRequestForAssetFromImageAtFileURL, I used this method and it worked perfect for 10 images (this part of code is repeated in a 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);        }}]; 


Processing a large batch of images on-device you have to be very careful about memory management even in the modern days of ARC. I had a large number of images to process (50+ with resizing) and ended up choosing the CGImageSourceCreateThumbnailAtIndex() as suggested by the answer here. It uses ImageIO which is supposed to be very efficient. The only issue I had remaining is that for some reason it would still hang on to memory unless I wrapped my for-loop in an @autoreleasepool {}

for (ALAsset *asset in assets) {        @autoreleasepool {            resizedImage = [self thumbnailForAsset:asset maxPixelSize:JPEG_MAX_DIMENSION];        // do stuff here    }}