Swift, dispatch_group_wait not waiting

I am trying to use grand central dispatch to wait for files to finish download before continuing. This question is a spin-off from this one: Swift (iOS), waiting for all images to finish downloading before returning.

I am simply trying to find out how to get dispatch_group_wait (or similar) to actually wait and not just continue before the downloads have finished. Note that if I use NSThread.sleepForTimeInterval instead of calling downloadImage, it waits just fine.

  • How do I have two post routes for the same class in RestKit
  • Top cell name changes when changing any cell name
  • How to AirPrint multiple print formatters in one go?
  • Call objective-c method from javascript
  • How to resize UITextView while typing inside it?
  • XcodeColors not working in XCode 5
  • What am I missing?

    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)")
                }
            }
        }
    } 
    

    Note: these downloads are in response to an HTTP POST request and I am using an HTTP server (Swifter) which does not support asynchronous operations, so I do need to wait for the full downloads to complete before returning a response (see original question referenced above for more details).

    4 Solutions Collect From Internet About “Swift, dispatch_group_wait not waiting”

    When using dispatch_group_async to call methods that are, themselves, asynchronous, the group will finish as soon as all of the asynchronous tasks have started, but will not wait for them to finish. Instead, you can manually call dispatch_group_enter before you make the asynchronous call, and then call dispatch_group_leave when the asynchronous call finish. Then dispatch_group_wait will now behave as expected.

    To accomplish this, though, first change downloadImage to include completion handler parameter:

    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)
        }
    }
    

    I’ve made that a completion handler that passes back the error code. Tweak that as you see fit, but hopefully it illustrates the idea.

    But, having provided the completion handler, now, when you do the downloads, you can create a group, “enter” the group before you initiate each download, “leave” the group when the completion handler is called asynchronously.

    But dispatch_group_wait can deadlock if you’re not careful, can block the UI if done from the main thread, etc. Better, you can use dispatch_group_notify to achieve the desired behavior.

    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)
        }
    }
    

    And you’d call it like so:

    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
    

    In Swift 3…

    let dispatchGroup = DispatchGroup()
    
    dispatchGroup.enter()
    // do something, including background threads
    
    dispatchGroup.leave()
    
    dispatchGroup.notify(queue: DispatchQueue.main) {
        // completion code
    }
    

    https://developer.apple.com/reference/dispatch/dispatchgroup

    The code is doing exactly what you are telling it to.

    The call to dispatch_group_wait will block until the block inside the call to dispatch_group_async is finished.

    The block inside the call to dispatch_group_async will be finished when the for loop completes. This will complete almost immediately since the bulk of the work being done inside the downloadImage function is being done asynchronously.

    This means the for loop finishes very quickly and that block is done (and dispatch_group_wait stops waiting) long before any of the actual downloads are completed.

    I would make use of dispatch_group_enter and dispatch_group_leave instead of dispatch_group_async.

    I would change your code to something like the following (not tested, could be typos):

    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);
            }
        }
    } 
    

    This change should do what you need. Each call to downloadImage enters the group and it doesn’t leave the group until the download completion handler is called.

    Using this pattern, the final line will execute when the other tasks are finished.

    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
    }