2017-01-17 1 views
2

Я использую Amazon S3 в качестве моей файловой системы хранения в приложении. У всех объектов моего объекта есть несколько изображений, связанных с ними, и в каждом из них хранятся только URL-адреса изображений, которые облегчают мою базу данных. Поэтому мне нужен эффективный способ загрузки нескольких изображений на S3 непосредственно из iOS, а при успешном завершении хранения их URL-адреса в объекте, который я отправляю на сервер. Я просмотрел в SDK и пример приложения Amazon предоставляет, но единственный пример я наткнулся для одной загрузки изображения, и выглядит следующим образом:Эффективный способ загрузки нескольких изображений на S3 из iOS

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 изображений это станет вложенным потому что мне придется ждать, пока каждая загрузка вернется успешно, прежде чем запускать следующую, а затем, наконец, отправит объект в мою БД. Есть ли эффективный, чистый код для меня для достижения моей цели?

URL, чтобы образец приложения GitHub Амазонки: https://github.com/awslabs/aws-sdk-ios-samples/tree/master/S3TransferUtility-Sample/Swift

+0

Вы решили это? Я ищу то же самое. – user2722667

ответ

1

Как я сказал в ответ Х. Аль-Амри, в случае вам нужно знать, когда последняя загрузка будет завершена, вы можете просто перебирайте массив данных и загружайте их сразу.

В Javascript есть библиотека (Async.js, я думаю), которая упростит выполнение фоновых операций над отдельными элементами массива и вызовет обратные вызовы, когда каждый закончит и когда весь массив будет закончен. Поскольку я не знаю ничего подобного для Swift, ваша интуиция, что вы должны связать загрузки, верна.

Как пояснил @DavidTamrazov в комментариях, вы можете связать свои звонки, используя рекурсию. Способ, которым я решил эту проблему, был немного более сложным, поскольку мои сетевые операции выполняются с использованием NSOperationQueue для объединения NSOperations. Я передаю массив изображений в пользовательский NSOperation, который загружает первое изображение из массива. Когда он завершится, он добавит еще один NSOperation в мой NSOperationsQueue с массивом оставшихся изображений. Когда массив заканчивается из изображений, я знаю, что я закончен.

Ниже приведен пример, который я вырезал из гораздо более крупного куска, который я использую. Рассматривайте его как псевдокод, потому что я его сильно отредактировал и не успел даже скомпилировать его. Но, надеюсь, достаточно ясно, как это сделать с 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) 
    } 
} 
+1

Это отличный вариант, который хорошо использует ранее существовавшие классы Swift, спасибо! Я закончил использование рекурсивной функции, которая приняла в качестве аргумента массив изображений и похожа на вашу логику, которая заканчивается после того, как массив, переданный ей, пуст. Я только вернусь к успешной загрузке изображения, чтобы обеспечить согласованность. –

+0

Спасибо @DavidTamrazov, вы правы, это не единственный способ, ведь способ, который вы сделали, более прост и лучший пример для SO. Я собираюсь обновить свой ответ, чтобы упомянуть об этом. –

-2

Почему бы не назвать uploadData() в цикле, передавая ему каждый раз, когда изображение, которое вы хотите загрузить?

Допустим, у вас есть массив NSData, ваш код будет выглядеть следующим образом:

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

Это правильно – WestonE

+2

Это неверно, так как при завершении итерации по вашему массиву вы инициировали все свои загрузки, но некоторые или все не были выполнены. Они будут продолжаться в фоновом режиме, и вы не узнаете, когда закончится последний. –

+0

Ваш метод неправильный. –