Swift loop closure function, then completion [duplicate] - swift

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.

Related

Using GCD how can a DispatchGroup have items run on the main queue from a background queue before (or just after) the group leaves, without deadlock?

Given some class Foo that calls Bar on a background thread to perform some work, how can Bar set some work to be done on the main thread without causing a deadlock, before the enclosing function returns the value it needs to return?
E.g. how can "asdf" print before "done" prints, before true is returned from func done() -> Bool in the example below?
import Dispatch
class Foo {
/// always called from the main queue
func done() -> Bool {
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
Bar().perform {
DispatchQueue.main.async { print("asdf") }
// "asdf" prints after "done" is printed
group.leave()
}
}
group.wait()
print("done")
return true
}
}
where Bar is simply:
struct Bar {
func perform(_ work: #escaping () -> Void) { work() }
}
I need Bar to be able to set some work that should be executed on the main queue before done() returns, without causing a deadlock (as would happen if we changed the above perform block to use DispatchQueue.main.sync, and (assuming done() is always called on the main thread, which it is).
The only solution I could come up with to print "asdf" before we print "done" is to change the done() function as follows:
func done() -> Bool {
let group1 = DispatchGroup()
var completion: (() -> Void)? = nil
group1.enter()
DispatchQueue.global().async(group: group1) {
Bar().perform {
completion = { print("asdf") }
group1.leave()
}
}
group1.wait()
completion?()
print("done")
return true
}
Here the completion block runs on the main thread before the function returns. However this feels kludgy and hacky... seems like something that GCD should just handle for us. But everything else I've tried will run after the function returns.
Thoughts?
The main queue under GCD is not re-entrant, and you cannot stop done from returning immediately except by blocking the main thread, nor (therefore) can you return a value from done after the first async method.
So, as you've been told in a comment, what you're trying to do is impossible.
You can call back into the main thread from inside the Bar completion handler, by way (for example) of another completion handler; but you cannot "wait" the way you are trying to do.
So for example:
struct Bar {
func perform(_ work: #escaping () -> Void) {
print("work")
work()
}
}
class Foo {
func done(_ f: #escaping (Bool) -> ()) {
DispatchQueue.global().async {
Bar().perform {
DispatchQueue.main.async {
// adjust the order of these two lines as desired
print("asdf")
f(true)
}
}
}
}
}
let f = Foo()
f.done { what in
print(what)
print("done")
}
Output:
work
asdf
true
done
Maybe that might work for you:
struct Bar {
func perform(work: #escaping () -> Void, completion: #escaping () -> Void) {
onBackGround(work) {
DispatchQueue.main.async {
print("asdf")
completion()
}
}
}
func done() {
print("done")
}
}
Call it:
bar = ..
bar.perform(work) {
self.done()
}

Completion Handler Causes Deinit To Not Be Called

I have a network task that contains a completion handler, allowing me to determine if and when the task completed successfully;
func performUpload(url: URL, completionHandler: #escaping (_ response: Bool) -> ()) {
photoSave.savePhotoRemote(assetURL: url) { response in
completionHandler(response)
}
}
I call this function from another UIView, by using the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
What I am noticing is that when I capture the response and do stuff, the class that called this function will never deinit. However, if I change my function to the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in }
and don't do anything with the response, the class will deinit.
What am I doing wrong with my function? I would like to capture the response accordingly, but not at the expense of my class not being released from memory.
You've got a retain cycle. Break it. Change
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
to
UploadHelper.shared.performUpload(url: fileAssetPath) { [unowned self] response in
if response {
// do stuff
}
}

Correct way to perform async operations sequentially

I need to perform an async operation for each element in an array, one at at time. This operation calls back on the main queue.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let queue = DispatchQueue(label: "Serial Queue")
queue.sync {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
group.wait()
}
}
print(results) // Never reached
completion()
}
The WebService call isn't calling back - which I think is telling me the main queue is blocked, but I can't understand why.
You should use group.notify() rather than group.wait(), since the latter is a synchronous, blocking operation.
I also don't see a point of dispatching to a queue if you only dispatch a single work item once.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
Maybe it's just a typo but basically don't run the queue synchronously.
Then instead of wait use notify outside(!) of the loop and print the results within the queue.
queue.async {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
I d'ont think your main queue is locked, otherwise you would probably have an infinite loading on your app, as if it crashed ( in MacOS that's for sure ).
Here is what worked for me, maybe it will help :
class func synchronize(completion: #escaping (_ error: Bool) -> Void) {
DispatchQueue.global(qos: .background).async {
// Background Thread
var error = false
let group = DispatchGroup()
synchronizeObject1(group: group){ error = true }
synchronizeObject2(group: group){ error = true }
synchronizeObject3(group: group){ error = true }
group.wait() // will wait for everyone to sync
DispatchQueue.main.async {
// Run UI Updates or call completion block
completion(error)
}
}
}
class func synchronizeObject1(group: DispatchGroup, errorHandler: #escaping () -> Void){
group.enter()
WebservicesController.shared.getAllObjects1() { _ in
// Do My stuff
// Note: if an error occures I call errorHandler()
group.leave()
}
}
If I would say, it may come from the queue.sync instead of queue.async. But I'm not an expert on Asynchronous calls.
Hope it helps

Call completion block when two other completion blocks have been called

I have a function doEverything that takes a completion block. It calls two other functions, doAlpha and doBeta which both have completion blocks. These two functions should run asynchronously. I want to call doEverything's completion block after both of the other functions have called their completion blocks.
Currently, it looks like this:
func doEverything(completion: #escaping (success) -> ())) {
var alphaSuccess = false
var betaSuccess = false
doAlpha { success in
alphaSuccess = success
}
doBeta { success in
betaSuccess = success
}
// We need to wait here
completion(alphaSuccess && betaSuccess)
}
doAlpha and doBeta should run at the same time and, once they've both completed, the completion block should be called with the result of alpha and beta.
I've read into dispatch groups and barriers but I'm not sure which is the most appropriate, how they both introduce new scope (with regards to the two variables I'm using) and how I should implement that.
Many thanks.
Grand Central Dispatch (GCD) is a pretty good choice of what are you trying to do here, you can achieve this by using DispatchQueue and DispatchGroup, as follows:
Swift 3:
func doEverything(completion: #escaping () -> ()) {
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
let group = DispatchGroup()
group.enter()
queue.async (group: group) {
print("do alpha")
group.leave()
}
group.enter()
queue.async (group: group) {
print("do beta")
group.leave()
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}
Or, you can implement it this way (which I find more readable):
func doEverything(completion: #escaping () -> ()) {
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
let group = DispatchGroup()
queue.async (group: group) {
print("do alpha")
}
queue.async (group: group) {
print("do beta")
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}
Note that I removed the success flag from the completion closure.
At this case, "do beta" (the execution of the second queue.async) won't be executed until "do alpha" (the execution of the first queue.async) finished, and that's because queue target is .main. If you want to let both of queue.async work concurrently, there is no need to create an extra queue, the same queue should does the work, by replacing:
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
with:
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent)
Now, the system will control over how both of queue.async tasks should work concurrently (and obviously, group.notify will be executed after the tow of the tasks finish).
Hope this helped.
Ahmad F's answer is correct but, as my functions with callbacks return immediately (like most do) and the callbacks are executed later, I don't need to create a new queue. Here's the original code with changes to make it work.
func doEverything(completion: #escaping (success) -> ())) {
var alphaSuccess = false
var betaSuccess = false
let group = DispatchGroup()
group.enter()
doAlpha { success in
alphaSuccess = success
group.leave()
}
group.enter()
doBeta { success in
betaSuccess = success
group.leave()
}
group.notify(queue: DispatchQueue.main) {
completion(alphaSuccess && betaSuccess)
}
}
I didn't really want to force the completion call onto the main thread, but hey ¯\_(ツ)_/¯

How do I cancel a completion handler?

I want to enhance the code below: when i click the "submitData" button, the added code should cancel the completion handler.
func returnUserData(completion:(result:String)->Void){
for index in 1...10000 {
print("\(index) times 5 is \(index * 5)")
}
completion(result: "END");
}
func test(){
self.returnUserData({(result)->() in
print("OK")
})
}
#IBAction func submintData(sender: AnyObject) {
self.performSegueWithIdentifier("TestView", sender: self)
}
Can you tell me how to do this?
You can use NSOperation subclass for this. Put your calculation inside the main method, but periodically check cancelled, and if so, break out of the calculation.
For example:
class TimeConsumingOperation : NSOperation {
var completion: (String) -> ()
init(completion: (String) -> ()) {
self.completion = completion
super.init()
}
override func main() {
for index in 1...100_000 {
print("\(index) times 5 is \(index * 5)")
if cancelled { break }
}
if cancelled {
completion("cancelled")
} else {
completion("finished successfully")
}
}
}
Then you can add the operation to an operation queue:
let queue = NSOperationQueue()
let operation = TimeConsumingOperation { (result) -> () in
print(result)
}
queue.addOperation(operation)
And, you can cancel that whenever you want:
operation.cancel()
This is, admittedly, a fairly contrived example, but it shows how you can cancel your time consuming calculation.
Many asynchronous patterns have their built-in cancelation logic, eliminating the need for the overhead of an NSOperation subclass. If you are trying to cancel something that already supports cancelation logic (e.g. NSURLSession, CLGeocoder, etc.), you don't have to go through this work. But if you're really trying to cancel your own algorithm, the NSOperation subclass handles this quite gracefully.