TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

dataTask() in loop

Forums > Swift

@Neph  

Hello everyone,

my app downloads a couple of files from a server, using a URLSessionDataTask. When a downloads finishes successfully (and without any errors), then it should start the next download (=next loop iteration). If there is any type of error, then the whole thing has to abort and display the error message through the calling function (I've got my own error enum class that doesn't throw an error for that). If it finishes without any errors, then it simply switches back to the calling function.

This function is called after another dataTask has finished (using a completion handler) but I never switch back to the main thread, so all of this is still running in the same background thread the previous task used.

My code (Swift 5, Xcode 14.2):

private func fileDownload(fileNames fns:[String]) {
    if !errorBool {
        print("Main thread: \(Thread.isMainThread)")
        let group = DispatchGroup()

        myloop:
        for f in fns {
            let url = getURL(f)

            group.enter()

            //DispatchQueue.global(qos: .background).async {
            let task = session.dataTask(with: url) {(data, response, error) in
                defer { group.leave() }
                print("Starting task completion!")

                if error != nil && data == nil {
                    self.errorBool = true
                    break myloop //TODO 1: "Cannot find label 'myloop' in scope", "self." doesn't help
                }

                if let httpResponse = response as? HTTPURLResponse {
                    //Do stuff with downloaded data, more error handling that sets the error flag and message
                }
            }
            task.resume()
            //}

            //TODO 2: How do I wait here for the task to finish?
            //group.wait()
            if errorBool {
                break myloop
            }
        }

        group.notify(queue: .main) {
            print("Done!")
            //Displays any errors in a popup (on the main thread) through the calling function
        }
    }
}

There are two things that I'm experiencing problems with:

  1. How do I break the loop from within the task if there's an error ("TODO 1")? I might be able to replace the first break with a return with the same result but I'm still wondering: Is it possible?
  2. More importantly, how do I wait at "TODO 2" until the task finishes, so I can break the loop if there are any errors? If I use group.wait() there, then the task never starts (deadlock?), even though it should automatically run on a background thread. I tried to switch to yet another background thread for the task (see inactive code above) but that didn't help either.

I thought that this is exactly what a DispatchGroup is used for but is it really or am I just using it incorrectly? Is there anything else that can accomplish a "wait" within this single function? I found a little bit of information about a Semaphore but no exact info how to do it in my case and some people even recommend not using semaphores anymore.

2      

In URLSession.dataTask, everything between the first brace and its matching closing brace is a "completion handler". Contrary to your print statement, it does not run when you start the task. It runs after the session completes its operation and returns either the data or an error.

If this is a GUI app, you do not want a loop waiting for data. Instead, after task.resume() initiates the http session, fileDownload() should immediately return to the code that called it.

However, instead of learning to use "completion handlers", a more modern approach would be for you to learn Swift Concurrency, more commonly referred to as async/await. Both Paul Hudson and Donny Wals have excellent eBooks on Swift Concurrency.

2      

@Neph  

Yes, I know that the task is started with task.resume() and only enters the "in" part of the code once the download finished (even if it failed) and that's exactly what I want/need. I edited the print.

Also yes, this is a GUI app but I display a progress popup while downloading, which includes the name of the file that's currently being downloaded. The app can't be used without that data, so people have to wait anyway, I just don't want to block the main thread, which might be what happens with group.wait() in my code (not sure why because the completion part still runs in the same background thread the task started).

I know about async/wait, the problem is that it can only be used with session.data but not with session.dataTask and I need the task's error messages/code. You also have to throw the errors with the async approach to get out of the loop and I've already got an error class that doesn't throw (for different reasons) that I'm also using everywhere else in my app (can't easily change it because of that).

I've played around with semaphores a bit after writing the post and I think I got it (iirc) but people advice against using those, so I'm back to square one.

Have you got a suggestion how I can do the downloads, as mentioned above, with the dataTask and without having to rely on throw?

2      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.