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:
- Call method to upload multiple images & pass in multiple images
- Iterate through images, create new AWS S3 upload request by calling special method (enter dispatch group before call)
- Use completion handler in upload method to return true/false based on if successful/error occurred (in completion handler, leave dispatch group)
- 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!