Handle Multiple File (Image) Uploads to AW

2020-07-24 16:31发布

问题:

I have been trying to research the best way to upload multiple images to AWS S3 from an iOS app. I've seen a few similar questions asked on StackOverflow, and the one that had the closest response to an "answer" was here: link . While I do not fully understand threads/dispatch groups/etc., I have attempted many ways to achieve a method to upload multiple images. How I was envisioning it was this:

  1. Call method to upload multiple images & pass in multiple images
  2. Iterate through images, create new AWS S3 upload request by calling special method (enter dispatch group before call)
  3. Use completion handler in upload method to return true/false based on if successful/error occurred (in completion handler, leave dispatch group)
  4. Wait for dispatch group tasks to finish, then use notify to handle next steps

I have created something similar along those lines; however, it does not seem to function 100%. I have implemented a while loop to check for "retryCount" to try x amount of times before alerting the user that the upload requests were unsuccessful. Below is the code I currently have - please let me know if this is approaching the process correctly/handling the multitude of requests properly/etc. Thank you very much!

static func uploadImages(imagesToUpload: [UIImage], complete: @escaping (String?) -> ()) {
    let folderKey = UUID().uuidString
    var retryArray = [String: Data]()
    var retryCount = 0

    for i in 0..<imagesToUpload.count {
        retryArray.updateValue(UIImagePNGRepresentation(imagesToUpload[i])!, forKey: "image\(i)")
        //retryArray["image\(i)"] = UIImagePNGRepresentation(imagesToUpload[i])
    }

    let imageGroup = DispatchGroup()
    imageGroup.enter()

    if(retryArray.keys.count > 0) {
        while(retryCount < 3) {
            //Iterate through retry array
            let uploadGroup = DispatchGroup()

            for (key, val) in retryArray {
                print("Trying to upload: \(key)")

                uploadGroup.enter()
                singleUpload(imageToUpload: val, keyVal: "public/\(folderKey)/\(key).png", complete: { (err) -> Void in
                    //If no error, remove key/value pair
                    if(!err) {
                        print("\(key) was successfully uploaded")
                        retryArray.removeValue(forKey: key)
                    }
                    uploadGroup.leave()
                })

            }
            uploadGroup.wait()
            if(retryArray.keys.count == 0) {
                break
            } else {
                retryCount = retryCount + 1
            }

        }
        imageGroup.leave()
    } else {
        imageGroup.leave()
    }

    imageGroup.notify(queue: DispatchQueue.main) {

        if(retryArray.keys.count > 0) {
            print("the images could not be uploaded")
            complete(nil)
            //execute code to delete folder in aws s3
        } else {
            print("all images uploaded successfully")
            complete(folderKey)
        }
    }
}

static func singleUpload(imageToUpload: Data, keyVal: String, complete: @escaping (Bool) -> ()) {

    var data: Data = Data() // Data to be uploaded
    data = imageToUpload

    let expression = AWSS3TransferUtilityUploadExpression()
    var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
    completionHandler = { (task, error) -> Void in
        //DispatchQueue.main.async(execute: {
            if let error = error {
                complete(true)
            }
            else{
                complete(false)
            }
        //})
    }

    let transferUtility = AWSS3TransferUtility.default()

    transferUtility.uploadData(data,
                               bucket: s3BucketName,
                               key: keyVal,
                               contentType: "image/png",
                               expression: expression,
                               completionHandler: completionHandler).continueWith {
                                (task) -> AnyObject! in
                                if let error = task.error {
                                    print("Error: \(error.localizedDescription)")
                                    complete(true)
                                }

                                if let _ = task.result {
                                    // Do something with uploadTask.
                                }
                                return nil;
    }
}

Also - I would be curious if there was a better approach to this problem in general. Rather than create a list of items to upload, then iterate through (x times based on retryCount), trying to upload items (& removing those that were successful), and then presenting the user with a result (all-or-nothing if 1 image doesn't upload). Is there a better route for user experience/efficiency? Thank you!