我在应用程序中使用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个图像,这将成为嵌套混乱,因为我必须等待每个上传成功返回,然后启动下一个,然后最终将对象发送到我的数据库。我有没有一个高效,干净的代码来实现我的目标?
亚马逊示例应用github的网址:https://github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferUtility-Sample/Swift
答案 0 :(得分:5)
以下是我用于将多个图片上传到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个小时。
来自NSTemporaryDirectory
的网址对于每个图片必须不同 !!!
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)
}
}
希望这会有所帮助!
答案 1 :(得分:1)
正如我在H. Al-Amri的回复中所说的那样,如果你需要知道上次上传的时间是什么,你就不能简单地遍历一系列数据并立即上传所有数据。
在Javascript中有一个库(我认为是Async.js),它可以很容易地对数组的各个元素进行后台操作,并在每个元素完成时以及整个数组完成时获得回调。由于我不知道Swift之类的东西,你必须链接上传的直觉是正确的。
正如@DavidTamrazov在评论中解释的那样,您可以使用递归将调用链接在一起。我解决这个问题的方式有点复杂,因为我的网络操作是使用NSOperationQueue链接NSOperations完成的。我将一组图像传递给一个自定义的NSOperation,它从阵列中上传第一个图像。完成后,它会向我的NSOperationsQueue添加另一个NSOperation,其中包含剩余图像数组。当阵列用完图像时,我知道我已经完成了。
以下是一个例子,我从一个更大的块中切出来。将其视为伪代码,因为我对其进行了大量编辑,并且没有时间编译它。但希望在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)
}
}