Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using DispatchGroup() in Swift 3 to perform a task?

With Swift 3, using GCD has changed to DispatchGroup(), and I'm trying to learn to use it in my code.

Currently I have a function in another class that attempts to download a file and output its speed. I like to have that function finish first because I assign its speed to a var that I will be using in the first class to perform other tasks that is dependent upon that var.

It goes something like this:

Second class:

func checkSpeed()
{
    // call other functions and perform task to download file from link

    // print out speed of download

    nMbps = speedOfDownload
}

First Class:

let myGroup = DispatchGroup()
let check: SecondClass = SecondClass()

myGroup.enter()

check.checkSpeed()

myGroup.leave()

myGroup.notify(queue: DispatchQueue.main, execute: {

    print("Finished all requests.")

    print("speed = \(check.nMbps)")
})

The problem is Finish all requests gets output first, thus returning nil for speed, then afterwards checkSpeed finishes and outputs the correct download speed.

I believe I'm doing this wrong, but I'm not sure?

How can I ensure that speed obtains the correct value after the completion of checkSpeed in my first class?

The details of checkSpeed is exactly the same as connectedToNetwork from GitHub: connectedness.swift

like image 684
Pangu Avatar asked Dec 17 '16 21:12

Pangu


People also ask

How does a DispatchGroup work?

You attach multiple work items to a group and schedule them for asynchronous execution on the same queue or different queues. When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing.

Why is DispatchGroup used in certain situations?

DispatchGroup allows for aggregate synchronization of work. It can be used to submit multiple different work items or blocks and track when they all complete, even though they might run on different queues.

How do you use DispatchWorkItem?

When to use DispatchWorkItem, wait(), notify(), and flags. A DispatchWorkItem encapsulates work to be performed on a dispatch queue or a dispatch group. It is primarily used in scenarios where we require the capability of delaying or canceling a block of code from executing.


2 Answers

You need to call DispatchGroup.leave() when the entered task has completed. So, in your code, myGroup.leave() needs be placed at the end of the completion handler inside your checkSpeed() method.

You may need to modify your code like this:

func checkSpeed(in myGroup: DispatchGroup) {
    //...
    ...downLoadTask... {...its completion handler... in
        //...

        // print out speed of download

        nMbps = speedOfDownload

        myGroup.leave() //<- This needs to be placed at the end of the completion handler
    }
    //You should not place any code after invoking asynchronous task.
}

And use it as:

myGroup.enter()

check.checkSpeed(in: myGroup)

myGroup.notify(queue: DispatchQueue.main, execute: {

    print("Finished all requests.")

    print("speed = \(check.nMbps)")
})

But, as noted in vadian's comment or Pangu's answer, you usually do not use DispatchGroup for a single asynchronous task.


ADDITION

I need to say, I strongly recommend completion handler pattern shown in Pangu's answer. It's a more general way to handle asynchronous tasks.

If you have modified your checkSpeed() to checkSpeed(completion:) as suggested, you can easily experiment DispatchGroup like this:

let myGroup = DispatchGroup()
let check: SecondClass = SecondClass()
let anotherTask: ThirdClass = ThirdClass()

myGroup.enter() //for `checkSpeed`
myGroup.enter() //for `doAnotherAsync`

check.checkSpeed {
    myGroup.leave()
}
anotherTask.doAnotherAsync {
    myGroup.leave()
}

myGroup.notify(queue: DispatchQueue.main) {

    print("Finished all requests.")

    print("speed = \(check.nMbps)")
}
like image 166
OOPer Avatar answered Nov 15 '22 18:11

OOPer


With hint provided in the comment and solution found here: from @vadian, since I am only performing one task, I used a async completion handler:

Second Class:

func checkSpeed(completion: @escaping () -> ())
{
    // call other functions and perform task to download file from link

    // print out speed of download

    nMbps = speedOfDownload
    completion()

}

First Class:

let check: SecondClass = SecondClass()

check.checkSpeed {
print("speed = \(check.nMbps)")

}

Now checkSpeed will complete first and speed is assigned the appropriate value.

like image 35
Pangu Avatar answered Nov 15 '22 19:11

Pangu