从iOS上传多个图像到S3的高效方法

我在应用程序中使用Amazon S3作为我的文件存储系统。 我的所有项目对象都有几个与之关联的图像,每个图像都只存储图像URL以保持我的数据库轻量级。 因此,我需要一种直接从iOS上传多个图像到S3的有效方法,并且在成功完成后将它们的URL存储在我发送到服务器的对象中。 我已经仔细阅读了亚马逊提供的SDK和示例应用程序,但我遇到的唯一示例是单个图像上传,如下所示:

func uploadData(data: NSData) { let expression = AWSS3TransferUtilityUploadExpression() expression.progressBlock = progressBlock let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility() transferUtility.uploadData( data, bucket: S3BucketName, key: S3UploadKeyName, contentType: "text/plain", expression: expression, completionHander: completionHandler).continueWithBlock { (task) -> AnyObject! in if let error = task.error { NSLog("Error: %@",error.localizedDescription); self.statusLabel.text = "Failed" } if let exception = task.exception { NSLog("Exception: %@",exception.description); self.statusLabel.text = "Failed" } if let _ = task.result { self.statusLabel.text = "Generating Upload File" NSLog("Upload Starting!") // Do something with uploadTask. } return nil; } } 

对于超过5个图像,这将成为嵌套混乱,因为我必须等待每个上载在启动下一个之前成功返回,然后最终将对象发送到我的数据库。 我是否有一个高效的代码清理来实现我的目标?

Amazon的示例应用程序github的URL: https : //github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferUtility-Sample/Swift

正如我在H. Al-Amri的回答中所说,如果您需要知道上次上传何时完成,您不能简单地遍历数据数组并立即上传所有数据。

在Javascript中有一个库(我认为是Async.js),它可以很容易地对数组的各个元素进行后台操作,并在每个元素完成时以及整个数组完成时获得回调。 由于我不知道Swift之类的东西,你必须链接上传的直觉是正确的。

正如@DavidTamrazov在评论中解释的那样,您可以使用递归将调用链接在一起。 我解决这个问题的方式有点复杂,因为我的网络操作是使用NSOperationQueue链接NSOperations完成的。 我将一组图像传递给一个自定义的NSOperation,它从arrays中上传第一个图像。 完成后,它会向我的NSOperationsQueue添加另一个NSOperation,其中包含剩余图像数组。 当arrays用完图像时,我知道我已经完成了。

以下是我使用的一个更大的块切出的一个例子。 将其视为伪代码,因为我对其进行了大量编辑,并且没有时间编译它。 但希望在NSOperations如何做到这一点的框架上已经足够清楚了。

 class NetworkOp : Operation { var isRunning = false override var isAsynchronous: Bool { get { return true } } override var isConcurrent: Bool { get { return true } } override var isExecuting: Bool { get { return isRunning } } override var isFinished: Bool { get { return !isRunning } } override func start() { if self.checkCancel() { return } self.willChangeValue(forKey: "isExecuting") self.isRunning = true self.didChangeValue(forKey: "isExecuting") main() } func complete() { self.willChangeValue(forKey: "isFinished") self.willChangeValue(forKey: "isExecuting") self.isRunning = false self.didChangeValue(forKey: "isFinished") self.didChangeValue(forKey: "isExecuting") print( "Completed net op: \(self.className)") } // Always resubmit if we get canceled before completion func checkCancel() -> Bool { if self.isCancelled { self.retry() self.complete() } return self.isCancelled } func retry() { // Create a new NetworkOp to match and resubmit since we can't reuse existing. } func success() { // Success means reset delay NetOpsQueueMgr.shared.resetRetryIncrement() } } class ImagesUploadOp : NetworkOp { var imageList : [PhotoFileListMap] init(imageList : [UIImage]) { self.imageList = imageList } override func main() { print( "Photos upload starting") if self.checkCancel() { return } // Pop image off front of array let image = imageList.remove(at: 0) // Now call function that uses AWS to upload image, mine does save to file first, then passes // an error message on completion if it failed, nil if it succceeded ServerMgr.shared.uploadImage(image: image, completion: { errorMessage ) in if let error = errorMessage { print("Failed to upload file - " + error) self.retry() } else { print("Uploaded file") if !self.isCancelled { if self.imageList.count == 0 { // All images done, here you could call a final completion handler or somthing. } else { // More images left to do, let's put another Operation on the barbie:) NetOpsQueueMgr.shared.submitOp(netOp: ImagesUploadOp(imageList: self.imageList)) } } } self.complete() }) } override func retry() { NetOpsQueueMgr.shared.retryOpWithDelay(op: ImagesUploadOp(form: self.form, imageList: self.imageList)) } } // MARK: NetOpsQueueMgr ------------------------------------------------------------------------------- class NetOpsQueueMgr { static let shared = NetOpsQueueMgr() lazy var opsQueue :OperationQueue = { var queue = OperationQueue() queue.name = "myQueName" queue.maxConcurrentOperationCount = 1 return queue }() func submitOp(netOp : NetworkOp) { opsQueue.addOperation(netOp) } func uploadImages(imageList : [UIImage]) { let imagesOp = ImagesUploadOp(form: form, imageList: imageList) self.submitOp(netOp: imagesOp) } } 

这是我用来将多个图像上传到S3的代码,同时使用DispatchGroup()

 func uploadOfferImagesToS3() { let group = DispatchGroup() for (index, image) in arrOfImages.enumerated() { group.enter() Utils.saveImageToTemporaryDirectory(image: image, completionHandler: { (url, imgScalled) in if let urlImagePath = url, let uploadRequest = AWSS3TransferManagerUploadRequest() { uploadRequest.body = urlImagePath uploadRequest.key = ProcessInfo.processInfo.globallyUniqueString + "." + "png" uploadRequest.bucket = Constants.AWS_S3.Image uploadRequest.contentType = "image/" + "png" uploadRequest.uploadProgress = {(bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in let uploadProgress = Float(Double(totalBytesSent)/Double(totalBytesExpectedToSend)) print("uploading image \(index) of \(arrOfImages.count) = \(uploadProgress)") //self.delegate?.amazonManager_uploadWithProgress(fProgress: uploadProgress) } self.uploadImageStatus = .inProgress AWSS3TransferManager.default() .upload(uploadRequest) .continueWith(executor: AWSExecutor.immediate(), block: { (task) -> Any? in group.leave() if let error = task.error { print("\n\n=======================================") print("❌ Upload image failed with error: (\(error.localizedDescription))") print("=======================================\n\n") self.uploadImageStatus = .failed self.delegate?.amazonManager_uploadWithFail() return nil } //=> Task completed successfully let imgS3URL = Constants.AWS_S3.BucketPath + Constants.AWS_S3.Image + "/" + uploadRequest.key! print("imgS3url = \(imgS3URL)") NewOfferManager.shared.arrUrlsImagesNewOffer.append(imgS3URL) self.uploadImageStatus = .completed self.delegate?.amazonManager_uploadWithSuccess(strS3ObjUrl: imgS3URL, imgSelected: imgScalled) return nil }) } else { print(" Unable to save image to NSTemporaryDirectory") } }) } group.notify(queue: DispatchQueue.global(qos: .background)) { print("All \(arrOfImages.count) network reqeusts completed") } } 

这是至关重要的部分,我至少失去了5个小时。 来自NSTemporaryDirectoryurl必须与每张图片不同 !!!

 class func saveImageToTemporaryDirectory(image: UIImage, completionHandler: @escaping (_ url: URL?, _ imgScalled: UIImage) -> Void) { let imgScalled = ClaimitUtils.scaleImageDown(image) let data = UIImagePNGRepresentation(imgScalled) let randomPath = "offerImage" + String.random(ofLength: 5) let urlImgOfferDir = URL(fileURLWithPath: NSTemporaryDirectory().appending(randomPath)) do { try data?.write(to: urlImgOfferDir) completionHandler(urlImgOfferDir, imgScalled) } catch (let error) { print(error) completionHandler(nil, imgScalled) } } 

希望这会有所帮助!

为什么不在循环中调用uploadData(),每次想要上传的图像都传递它?

假设你有一个NSData数组,你的代码看起来像:

 for imgData in array { uploadData(data: imgData) }