How to know when all async functions have completed? - ios

I need to be able to call a completion block once all the asynchronous functions have completed. However, they don't all have completion blocks. This is my code:
func pauseStream(completion: #escaping () -> ()) {
disconnectFromSession()
model.publishers = []
model.pauseStream() { result in
}
}
disconnectFromSession is an asynchronous function that when completed fires a callback function didDisconnectFromSession in a delegate class.
Setting model.publishers = [] posts a Notification to NotificationCenter that is received by a class which then updates a UI.
Finally model.pauseStream() has a completion block to let me know when it's completed.
What I need to do is once all the asynchronous parts of the code have completed, I want to call the completion() block of my pauseStream function. What's the best way to do this? Unfortunately I can't change them to all have a completion block.

You generally use dispatch groups for this sort of thing. The trick here is that if you need to wait for disconnectFromSession to call its completion handler, then need to have didDisconnectFromSession call leave for the dispatch group.
So create ivar for the dispatch group:
let group = DispatchGroup()
Have your pauseStream use this DispatchGroup to call its completion handler when enter calls are offset by their corresponding leave calls:
func pauseStream(completion: #escaping () -> ()) {
group.enter()
disconnectFromSession() // this will call `leave` in its delegate method
model.publishers = []
group.enter()
model.someAsynchronousMethod() { result in
defer { group.leave() }
...
}
group.notify(queue: .main) {
completion()
}
}
And, your didDisconnectFromSession, would call that corresponding leave:
func didDisconnectFromSession(...) {
group.leave()
...
}

Related

iOS async function not in order

I'm new in async stuff in swift, ans I'm trying to implement a simple async call to a function.
import UIKit
func doAsyncStuff ( completionHandler: (_ result: Double) -> Void) {
print("start async")
sleep(5) // to simulate long work
let result: Double = 123.456
completionHandler(result)
}
print("A")
doAsyncStuff() { result in
print(result)
}
print("B")
When I execute it, I get
A
start async
123.456
B
As the func is called asynchronously, I would have expected to get
A
start async
B
123.456
Where did I go wrong ?
Thanks a lot
The issue is that you're calling sleep, which is a synchronously blocking function, so you block the main thread for 5 seconds and nothing else can be executed in the meantime. You should use DispatchQueue.asyncAfter to test simple async calls.
You can also know that your function is not asynchronous, since you didn't receive a compiler error for not marking your closure as #escaping, which you need to do for async completion handlers.
func doAsyncStuff(completionHandler: #escaping (_ result: Double) -> Void) {
print("start async")
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
completionHandler(123.456)
}
}
Your function is not asynchronous. Each line of code is being run synchronously (i.e. waiting for the line before to complete before executing). So the sleep(5) pauses execution in the middle of the function and print("B") is not called until after the function returns.
You can use Grand Central Dispatch (GCD) to run code asynchronously on a different thread. Here is an example:
import UIKit
func doAsyncStuff ( completionHandler: (_ result: Double) -> Void) {
DispatchQueue.global(qos: .background).async { [weak self] in
print("start async")
sleep(5)
let result: Double = 123.456
completionHandler(result)
}
}
print("A")
doAsyncStuff() { result in
print(result) // This is your completion handler code
}
print("B")
Beware, you are calling your function on the same thread, therefore it will be synchronous!
To call it in an asynchronous fashion, create a background thread/task, and call your function from that background thread. One way of doing it:
print("A")
DispatchQueue.global(qos: .background).async {
//this is a background thread, do in it your async stuff
doAsyncStuff() { result in
print(result)
}
}
print("B")
Use DispatchQueue.main.asyncAfter for an asynchronous call in swift.
So your code should look like..
func doAsyncStuff ( completionHandler: (_ result: Double) -> Void) {
print("start async")
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
// your code goes from here
let result: Double = 123.456
completionHandler(result)
}
}

Accessing main queue via DispatchGroup vs. DispatchQueue

I'm using DispatchGroup in a class that runs on a background thread. Occasionally, I need to update the UI, so I call the following code:
dispatchGroup.notify(queue: .main) {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
Unfortunately, nothing happens. However, if I call the same code, via DispatchQueue.main.async { }, like so:
DispatchQueue.main.async {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
...the delegate call gets made. I was under the impression dispatchGroup.notify(queue: .main) { } is equivalent to DispatchQueue.main.async { }.
Why are these not the same?
Is your dispatchGroup empty (i.e. have no blocks running) at the time when you call notify(queue:)? If not, as documentation states dispatchGroup.notify(queue:)
Schedules a work item to be submitted to a queue when a group of previously submitted block objects have completed.
Which means that your closure will be executed only after the last leave() call, when the group becomes empty. And, of course, enter()s and leave()s must be balanced.
Consider the following example:
let group = DispatchGroup()
group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}
group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}
group.notify(queue: .main) {
print("all set")
}
In this example all set will be printed only after two callbacks with group.leave() execute.
On the other hand, DispatchQueue.main.async() immediately submits the block to the target queue but it will not necessarily start right after that – there could be running, for example, async block with the .barrier flag.
Update: Implementation of the example above using DispatchQueue (hopefully, it makes things clear):
let group = DispatchGroup()
group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}
group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}
group.wait() // waits synchronously for the submitted work to complete
DispatchQueue.main.async {
print("all set")
}

Swift: Deadlock with DispatchGroup

I've got some issue with CoreData concurrency.
I can't do context.perform while a destination thread is blocked with DispatchGroup.
Here is a simple example which shows the issue:
func upload(objects: [NSManagedObject]) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.wait() // current thread is blocked here
someAdditionalWorkToDoInSameThread()
}
func upload(object: NSManagedObject, completion: ()->()) {
let context = object.managedObjectContext
performAlamofireRequest(object) {
context.perform {
// can't reach here because the thread is blocked
update(object)
completion()
}
}
}
Please, help me to reimplement this properly. Thanks.
Using notify on dispatch group instead of wait, should resolve your issues.
Calling wait() blocks current thread for completion of previously submitted work.
notify(queue:execute:) will notify the queue that you passed as argument that the group task has been completed.
func upload(objects: [NSManagedObject], completion: ()->()) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}

How to execute a function that has to run on main queue when main queue is paused?

The function below checks whether protected data is available.
func isProtectedDataAvailable(_ completion: ((_ isProtectedDataAvailable: Bool)->Void)?) {
DispatchQueue.main.async {
completion?(UIApplication.shared.isProtectedDataAvailable)
}
}
How can I change the function so that it can be called even when the main thread is paused in a setting like this:
let dispatch = DispatchGroup()
dispatch.enter()
isProtectedDataAvailable { isProtectedDataAvailable in
//...
dispatch.leave()
}
dispatch.wait()
The wait() above would pause the main queue and the completion handler would never be called.

Pooling completion handlers together such that method completes once multiple closures are executed

I have a scenario where I want to perform three distinct asynchronous tasks in parallel. Once all three tasks are complete, I then want the calling method to be aware of this and to call its own completion handler.
Below is a very simplified version of the logic for this:
class ViewController: UIViewController {
func doTasks(with object: Object, completionHandler: () -> Void) {
// Once A, B & C are done, then perform a task
wrapupTask()
// When task is complete, call completionHandler
completionHandler()
}
}
fileprivate extension ViewController {
func taskA(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskB(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskC(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
}
I could easily chain the handlers together, but then the task will likely take longer and the code will suck.
Another item I considered was a simple counter that incremented each time a task completed, and then once it hit 3, would then call the wrapupTask() via something like this:
var count: Int {
didSet {
if count == 3 {
wrapupTask()
}
}
}
Another option I have considered is to create an operation queue, and to then load the tasks into it, with a dependency for when to run my wrap up task. Once the queue is empty, it will then call the completion handler. However, this seems like more work than I'd prefer for what I want to accomplish.
My hope is that there is something better that I am just missing.
Just to pick up on what OOPer said, you are looking for DispatchGroup. In the following, the calls to taskA, taskB, and taskC are pseudo-code, but everything else is real:
func doTasks(with object: Object, completionHandler: () -> Void) {
let group = DispatchGroup()
group.enter()
taskA() {
// completion handler
group.leave()
}
group.enter()
taskB() {
// completion handler
group.leave()
}
group.enter()
taskC() {
// completion handler
group.leave()
}
group.notify(queue: DispatchQueue.main) {
// this won't happen until all three tasks have finished their completion handlers
completionHandler()
}
}
Every enter is matched by a leave at the end of the asynchronous completion handler, and only when all the matches have actually executed do we proceed to the notify completion handler.

Resources