2016-01-25 8 views
5

私は、グランドセントラルディスパッチを使用してファイルのダウンロードが完了するのを待ってから続行します。この質問は、これからのスピンオフです:Swift (iOS), waiting for all images to finish downloading before returningSwift、dispatch_group_wait not waiting

私は、実際に待機するためにdispatch_group_wait(または同様のもの)を取得する方法を見つけようとしており、ダウンロードが完了する前に続行するだけではありません。 downloadImageを呼び出す代わりにNSThread.sleepForTimeIntervalを使用すると、それは正常に待機することに注意してください。

私には何が欠けていますか?

class ImageDownloader { 

    var updateResult = AdUpdateResult() 

    private let fileManager = NSFileManager.defaultManager() 
    private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true) 

    private let group = dispatch_group_create() 
    private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL) 

    func downloadImages(imageFilesOnServer: [AdFileInfo]) { 

     dispatch_group_async(group, downloadQueue) { 

      for serverFile in imageFilesOnServer { 
       print("Start downloading \(serverFile.fileName)") 
       //NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work 
       self.downloadImage(serverFile) 
      } 
     } 
     dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why? 

     print("All Done!") // It gets here too early! 
    } 

    private func downloadImage(serverFile: AdFileInfo) { 

     let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName) 

     Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath } 
     .response { _, _, _, error in 
      if let error = error { 
       print("Error downloading \(serverFile.fileName): \(error)") 
      } else { 
       self.updateResult.filesDownloaded++ 
       print("Done downloading \(serverFile.fileName)") 
      } 
     } 
    } 
} 

注:これらのダウンロードは、HTTP POSTリクエストに応答していると私は非同期操作をサポートしていないHTTPサーバ(迅速確実)を使用していますので、私は完全ダウンロードを返す前に完了するのを待つ必要がありますかレスポンス(上記のオリジナルの質問を参照してください)

答えて

10

、自分自身、非同期のあるメソッドを呼び出すためにdispatch_group_asyncを使用して、グループはすぐに非同期タスクのすべてが開始されたとして終了しますが、それらが終了するのを待つことはありません。代わりに、非同期呼び出しを行う前に手動でdispatch_group_enterを呼び出してから、非同期呼び出しが終了したときにdispatch_group_leaveを呼び出してください。その後、dispatch_group_waitは期待どおりに動作します。

はいえ、最初の変更 downloadImageが完了ハンドラパラメータ含めるために、これを実現するには、次の

private func downloadImage(serverFile: AdFileInfo, completionHandler: (NSError?)->()) { 
    let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName) 

    Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath } 
     .response { _, _, _, error in 
      if let error = error { 
       print("Error downloading \(serverFile.fileName): \(error)") 
      } else { 
       print("Done downloading \(serverFile.fileName)") 
      } 
      completionHandler(error) 
    } 
} 

私が作ったが、そのエラーコードをバック渡し完了ハンドラ。それを微調整してみてください。しかし、うまくいけばそれはそのアイデアです。

ただし、ダウンロードを実行すると、グループを作成し、ダウンロードを開始する前にグループを入力し、完了ハンドラが非同期に呼び出されたときにグループを「離れる」ことができます。

dispatch_group_waitあなたが慎重でない場合はデッドロックが発生し、メインスレッドなどからUIをブロックすることができます。より良い方法は、dispatch_group_notifyを使用して目的の動作を達成できます。

func downloadImages(imageFilesOnServer: [AdFileInfo], completionHandler: (Int) ->()) { 
    let group = dispatch_group_create() 

    var downloaded = 0 

    for serverFile in imageFilesOnServer { 
     dispatch_group_enter(group) 
     print("Start downloading \(serverFile.fileName)") 
     self.downloadImage(serverFile) { error in 
      if error == nil { 
       downloaded += 1 
      } 
      dispatch_group_leave(group) 
     } 
    } 
    dispatch_group_notify(group, dispatch_get_main_queue()) { 
     completionHandler(downloaded) 
    } 
} 

そして、あなたがそうのようにそれを呼びたい:スウィフト3では

downloadImages(arrayOfAdFileInfo) { downloaded in 
    // initiate whatever you want when the downloads are done 

    print("All Done! \(downloaded) downloaded successfully.") 
} 

// but don't do anything contingent upon the downloading of the images here 
+0

ロブは、私はここで同様の問題があります。http ://stackoverflow.com/questions/37201735/how-to-make-inner-async-request-complete-first-before-completing-outer-async-reqあなたが助けることができると思いますか? – Pangu

+0

偉大な答え、これはFirebaseと似たようなことをやってくれました。 – kelsheikh

+0

** 'dispatch_group_wait'のポイントは、あなたが慎重でないとデッドロックする可能性があります**私を救った! – fujianjin6471

1

コードは、あなたがそれを伝えているものとまったく同じです。

dispatch_group_waitへのコールは、dispatch_group_asyncへのコール内のブロックが終了するまでブロックされます。

forループが完了すると、dispatch_group_asyncへのコール内のブロックが終了します。 downloadImage関数内で実行される作業の大部分が非同期で実行されているので、これはほぼ直ちに完了します。

これは、実際のダウンロードが完了する前に、ループが非常に迅速に終了し、そのブロックが完了したこと(そして、dispatch_group_waitが待機を停止する)を意味します。

dispatch_group_asyncの代わりにdispatch_group_enterdispatch_group_leaveを使用します。

私は(テストしていません、タイプミスかもしれない)、次のようなものにあなたのコードを変更します

class ImageDownloader { 

    var updateResult = AdUpdateResult() 

    private let fileManager = NSFileManager.defaultManager() 
    private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true) 

    private let group = dispatch_group_create() 
    private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL) 

    func downloadImages(imageFilesOnServer: [AdFileInfo]) { 

     dispatch_async(downloadQueue) { 
      for serverFile in imageFilesOnServer { 
       print("Start downloading \(serverFile.fileName)") 
       //NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work 
       self.downloadImage(serverFile) 
      } 
     } 

     dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why? 

     print("All Done!") // It gets here too early! 
    } 

    private func downloadImage(serverFile: AdFileInfo) { 
     dispatch_group_enter(group); 

     let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName) 

     Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath } 
     .response { _, _, _, error in 
      if let error = error { 
       print("Error downloading \(serverFile.fileName): \(error)") 
      } else { 
       self.updateResult.filesDownloaded++ 
       print("Done downloading \(serverFile.fileName)") 
      } 
      dispatch_group_leave(group); 
     } 
    } 
} 

この変更は、何が必要行う必要があります。 downloadImageへの各コールはグループに入り、ダウンロード完了ハンドラが呼び出されるまでグループを離れることはありません。

0

このパターンを使用すると、他のタスクが終了すると最終行が実行されます。

let group = dispatch_group_create() 

dispatch_group_enter(group) 
// do something, including background threads 
dispatch_group_leave(group) // can be called on a background thread 

dispatch_group_enter(group) 
// so something 
dispatch_group_leave(group) 

dispatch_group_notify(group, mainQueue) { 
    // completion code 
} 
関連する問題