So I have situation when I've want do some async stuff, post status on Facebook and get notified when it's done posting. I was hoping that dispatch_group_async will do the job but now I hit the wall.
Here is how at this moment logic look like.
Func
func saveAndPost() {
//1. save
dispatch_group_async(group, queue) { () -> Void in
print("saving to DB")
//some func
print("saved to DB")
}
//2. post
dispatch_group_async(group, queue, { () -> Void in
print("publishing on FB")
//some func
//async request to ACAccount {
print("access")
//async performRequestWithHandler{
print("posted")
//anwser from request
}
}
}
print("published on FB")
})
dispatch_group_notify(group, queue, { () -> Void in
print("done")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//go to next view
})
})
}
Output
saving to DB
saved to DB
publishing on FB
published on FB
done
post
posted
My goal is to get notified when whole posting process is done. Maybe this is not possible with dispatch_group_async and I have to use KVO or even PromiseKit?
SOLUTION
David got me thinking and dispatch_group_enter & 'dispatch_group_leave' right what I needed. So at this moment my logic look like this.
func saveAndPost() {
//1. save
dispatch_group_async(group, queue) { () -> Void in
print("saving to DB")
//some func
print("saved to DB")
}
//2. post
dispatch_group_enter(group)
print("publishing on FB")
//some func
//async request to ACAccount {
print("access")
//async performRequestWithHandler{
print("posted")
//anwser from request
dispatch_group_leave(group)
}
}
}
print("published on FB")
dispatch_group_notify(group, queue, { () -> Void in
print("done")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//go to next view
})
})
}
As you can see part 2. is now modified.
My preferred method to tackle this when dealing with async calls:
var asyncGroup = dispatch_group_create()
dispatch_group_enter(asyncGroup)
asyncCall1.start() {
//end of callback
dispatch_group_leave(asyncGroup)
}
dispatch_group_enter(asyncGroup)
asyncCall2.start() {
asyncCall3.start() {
asyncCall4.start() {
dispatch_group_leave(asyncGroup)
}
}
}
dispatch_group_notify(asyncGroup, dispatch_get_main_queue(), {
println("done")
})
asyncCall1.start() { ... } and asyncCall2.start() { ... } are just sorta psuedocode to show you how it works.
Every dispatch_group_enter call needs to be balanced by a dispatch_group_leave call or you'll never get into the dispatch_group_notify block.
Related
This question already has answers here:
DispatchGroup logical workflow
(2 answers)
Closed 1 year ago.
I have two methods in the completeOnboarding method and both of them have network operation which should be done in the background thread as follows. However, I am wondering if I am doing why completion(true) gets called first, how could I able to handle that issue?
DispatchQueue.global(qos: .background).async {
self?.completeOnboarding( completion: { (success) in
DispatchQueue.main.async {
if success {
print("success")
} else {
print("failed")
}
}
})
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
// has network post operation
classLocation() { (success) in
if !success {
completion(false)
return
}
}
completion(true)
}
The final completion(true) is not waiting for classLocation() and classRegistration() calls. If you have multiple network calls and you want to wait for all of them to finish you could (one approach) add them to a DispatchGroup and wait for that one to finish:
func dispatchAndWait(completion: #escaping () -> Void) {
func networkOne(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
func networkTwo(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
// Create a DispatchGroup and add both calls
let dispatchGroup = DispatchGroup()
// Enter first network call
dispatchGroup.enter()
networkOne { success in
print("[DEBUG] Complete networkOne with success: \(success)")
// Exit first network call
dispatchGroup.leave()
}
// Enter second network call
dispatchGroup.enter()
networkTwo { success in
print("[DEBUG] Complete networkTwo with success: \(success)")
// Exit second network call
dispatchGroup.leave()
}
// notify gets called when all tasks have exited
dispatchGroup.notify(queue: DispatchQueue.main) {
completion()
}
}
One more advise:
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
will never complete in case of success==true, you should make sure that the completion is called on every path
classRegistration() {(success) in
// ... do whatever needs to be done here
completion(success)
}
Assuming classRegistration needs to succeed for classLocation to begin --
quick & dirty --
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if success {
// has network post operation
classLocation() { (success) in
completion(success)
}
} else {
completion(false)
}
}
}
a proper way (others include - NSOperations with dependency, Dispatch group)
func completeOnboarding(completion: #escaping(Bool) -> Void){
let serialQueue = DispatchQueue(label: "classname.serial")
var proceedWithSuccess = true
serialQueue.async {
serialQueue.suspend() //run 1 operation at a time
classRegistration() {(success) in
proceedWithSuccess = success
serialQueue.resume() //let next operation run
}
}
serialQueue.async {
guard proceedWithSuccess else { return }
serialQueue.suspend()
classLocation() { (success) in
proceedWithSuccess = success
serialQueue.resume()
}
}
serialQueue.async {
completion(proceedWithSuccess)
}
}
If you want classLocation() to fire even if registration fails - just get rid of guard statement above.
If it were up to me I'd use a custom NSOperation subclass for Async operation & explicitly mention dependency between operations but it needs a ton of boilerplate code (perhaps something to look into later); serial queue (or dispatch group from the other answer) oughta be enough for you in this case though.
I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.
I want to run some tasks which are dependent on each other so should be performed in an order. Currently, it blocks my UI thread and also there is some issue in ordering.
Couple of questions regarding this:
Tasks are not performed in correct order. What change to be made if we want them to be performed one after other
Is the code optimised in terms of memory usage and resource consumption? How it can be made more optimised?
Do we need global queues inside function call also as shown in code below?
Here are my code details. I have created some serial queues as follows:
var Q0_sendDisplayName=dispatch_queue_create("Q0_sendDisplayName",DISPATCH_QUEUE_SERIAL)
var Q1_fetchFromDevice=dispatch_queue_create("fetchFromDevice",DISPATCH_QUEUE_SERIAL)
var Q2_sendPhonesToServer=dispatch_queue_create("sendPhonesToServer",DISPATCH_QUEUE_SERIAL)
I have an idea that serial queues perform tasks in order so i have called my tasks on serial queues. Here is my code:
dispatch_sync(Q0_sendDisplayName,
{
self.sendNameToServer(displayName){ (result) -> () in
dispatch_sync(self.Q1_fetchFromDevice,
{
self.SyncfetchContacts({ (result) -> () in
dispatch_sync(self.Q2_sendPhonesToServer,
{ self.SyncSendPhoneNumbersToServer(self.syncPhonesList, completion: { (result) in
//.......
//....
The code inside these functions is also running on global queue. Dont know if it is a correct way to code. I have used completion handlers to notify that method has completed executing. Here is the code of function1:
func sendNameToServer(var displayName:String,completion:(result:Bool)->())
{
Alamofire.request(.POST,"\(urlToSendDisplayName)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
return completion(result: true) //......
Here is the code of function2. This function is long as it reads whole contacts book so i have placed it inside global queue(dont know if it is right way). I call completion handler on main queue. Here is code:
func SyncfetchContacts(completion:(result:Bool)->())
{
let contactStore = CNContactStore()
var keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey, CNContactImageDataKey]
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)){
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
}
dispatch_async(dispatch_get_main_queue())
{
completion(result: true)
}
}
//........
Here is the code for function3 which again inside has a global queue(dont know if its right) and calls completion handler on main queue.
func SyncSendPhoneNumbersToServer(phones:[String],completion: (result:Bool)->()){
Alamofire.request(.POST,"\(url)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0))
{
//enter some large data in database in a loop
dispatch_async(dispatch_get_main_queue())
{
return completion(result: true)
}
}//......
In SyncfetchContacts you are calling the completion handler before contactStore.enumerateContactsWithFetchRequest has finished, outside of its completion closure.
Simply move it in there:
func SyncfetchContacts(completion:(result:Bool)->()) {
...
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
// here ...
dispatch_async(dispatch_get_main_queue()) {
completion(result: true)
}
}
// ... not here.
// dispatch_async(dispatch_get_main_queue()) {
// completion(result: true)
// }
}
}
In my example code below, I call complete(false) on failure. However, since I'm using a DispatchGroup object to make sure all asynchronous requests are complete, I cannot just call syncGroup.leave() on failure, as the notify will be called, which contains complete(true), making this function return true, when it should be returning false for failure.
Am I correct in not calling syncGroup.leave() on failure to complete my function correctly? Or should I be calling syncGroup.leave() and somehow trying to determine what the result is, so I can return false on failure?
let syncGroup = DispatchGroup()
syncGroup.enter()
for track in unsynced {
register(time: time, withCompletion: { (success: Bool) -> () in
if success {
self.debug.log(tag: "SyncController", content: "Registered")
syncGroup.leave()
}
else {
complete(false)
}
})
}
//all requests complete
syncGroup.notify(queue: .main) {
self.debug.log(tag: "SyncController", content: "Finished registering")
complete(true)
}
You have to enter the group within your for loop. You might want to introduce an additional error flag.
Example implementation:
var fail = false
let syncGroup = DispatchGroup()
for track in unsynced {
syncGroup.enter()
register(time: time, withCompletion: { (success: Bool) -> () in
if success {
self.debug.log(tag: "SyncController", content: "Registered")
syncGroup.leave()
}
else {
fail = true
syncGroup.leave()
}
})
}
//all requests complete
syncGroup.notify(queue: .main) {
if fail {
complete(false)
} else {
self.debug.log(tag: "SyncController", content: "Finished registering")
complete(true)
}
}
I got quite a lot of confusion on async tasks in swift. What i want to do is something like this...
func buttonPressed(button: UIButton) {
// display an "animation" tell the user that it is calculating (do not want to freeze the screen
// do some calculations (take very long time) at the background
// the calculations result are needed to update the UI
}
I tried to do something like this:
func buttonPressed(button: UIButton) {
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) { () -> Void in
// display the animation of "updating"
// do the math here
dispatch_async(dispatch_get_main_queue(), {
// update the UI
}
}
}
However, I found that the UI is updated without waiting my calculation to be completed. I am quite confused on the use of async queue. Anyone help? Thanks.
You need a function with a asynchronous completion handler.
At the end of the calculation call completion()
func doLongCalculation(completion: () -> ())
{
// do something which takes a long time
completion()
}
In the buttonPressed function dispatch the calculate function on a background thread and after completion back to the main thread to update the UI
func buttonPressed(button: UIButton) {
// display the animation of "updating"
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
self.doLongCalculation {
dispatch_async(dispatch_get_main_queue()) {
// update the UI
print("completed")
}
}
}
}
dispatch_queue_t dispatchqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatchqueue, ^(void){
while ([self calculate]) {
NSLog(#"calculation finished");
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI
});
}
});
- (BOOL)calculate
{
//do calculation
//return true or false based on calculation success or failure
return true;
}