Call Completion Block When Two Other Completion Blocks Have Been Called

Function that executes after multiple completion block has finished

You will need either use dispatch_group or use functional reactive programming library like RxSwift to achieve it if you does not want to manage flags.

However, you can just use one counter flag and just make a function call or use NSNotification if is for another ViewController.

In one of my project, I need to ensure that at least 3 of the 4 completion block is completed before calling some function. I do it something like this:

class TestClass: UIViewController {
var numberOfBlockCompleted = 0

func blockOneDownloadImageDescription(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}

func blockTwoDownloadImageData(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}

func blockThreeDownloadImageDesc(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}


func allDataDownloadComplete() {
if numberOfBlockCompleted == 3 {
//do something
}
}
}

In my opinion, it depend largely on how complex is the app. If is just for one or two part, a flag is good enough. However, if the app depending largely on chaining network calls and fetching from different server that need to wait for one or another to be completed like a live stocks app then a strong knowledge of GCD or using functional reactive programming will make your job easier in the long run.

how to call the block inside the function, who is waiting for another function's block completion inside itself?

b function completion block should be called with in the c function completion handler.

function b should be like:

func b(_ onCompletion: @escaping fetchCompletionBlock) {
c({
print("c called completed")
onCompletion()
})
}

How to execute two completion blocks in a single function and pass the data of the completion block to next view controller?

Your completion handler will be called twice, once for "answers" and once for "questions". They could come in either order, so you should pass an additional type in the completion to know which you have received. Use a [String : [Any]] dictionary to collect the two arrays, and call self.performSegue(withIdentifier:sender:) when you've received both arrays and stored them in the dictionary arrays.

In prepare(for:sender:) unpack the sender dictionary and assign the values:

@IBAction func GoToAnswerPage(_ sender: Any) {

var arrays = [String : [Any]]()

self.getData(refe: JoinCodeTextField.text!) { (array, type) in

arrays[type] = array

if arrays.count == 2 {
self.performSegue(withIdentifier:"JoinToAnswerPage",sender: arrays)
}
}

}

func getData(refe: String, completion: @escaping (([Any], String) -> ())) {
var questionArray = [Any]()
var answerArray = [Any]()
let ref = Database.database().reference(fromURL: "https://pollapp-30419.firebaseio.com/").child("Questions/\(refe)/")
ref.child("Question_And_Options").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
questionArray.append(value)
}
completion(questionArray, "question")
})
ref.child("Answer_Key").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
answerArray.append(value)
}
completion(answerArray, "answer")
})
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let joinViewController = segue.destination as? JoinAnswerViewController
else {
return
}

guard let arrays = sender as? [String : [Any]],
let answers = arrays["answer"] as? [String],
let questions = arrays["question"] as? [String]
else { return }

joinViewController.answers = answers
joinViewController.options = questions
}

Note: When the user presses a button, they should get an immediate response. Since you are loading the data from the network, there may be a delay making the user wonder if anything is happening. It would be better to pass JoinCodeTextField.text! to JoinAnswerViewController and let it load the question/answer data. JoinAnswerViewController could display a UIActivityIndicatorView (spinner) while the data is loading to let the user know the data is coming. Once you have both arrays, you can set up the JoinAnswerViewController.

Objective C: Is there a way to call a completion block for a method in another method?

You can pass completion blocks around and then call them from your final method which handles all the get requests. I usually make completion blocks that are going to be reused typedefs for brevity. Here's an example of what I mean (I added a second example method that also passes through to the center getRequestWithURLString:onSuccess:onFailure: method):

LLFakeManager.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void (^_Nullable SuccessCompletionBlock)(id responseObject);
typedef void (^_Nullable FailureCompletionBlock)(NSError *error);

@interface LLFakeManager : NSObject
- (void)getUserInfoOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock;
- (void)getBooksCheckedOutOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock;
@end

NS_ASSUME_NONNULL_END

LLFakeManager.m

#import "LLFakeManager.h"

@interface LLFakeManager()
- (void)getRequestWithURLString:(NSString *)urlString
onSuccess:(SuccessCompletionBlock)successBlock
onFailure:(FailureCompletionBlock)failureBlock;
@end

@implementation LLFakeManager

- (void)getUserInfoOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock {
NSString *urlStr = @"FakeUserUrlPath";
[self getRequestWithURLString:urlStr onSuccess:successBlock onFailure:failureBlock];
}

- (void)getBooksCheckedOutOnSuccess:(SuccessCompletionBlock)successBlock onFailure:(FailureCompletionBlock)failureBlock {
NSString *urlString = @"FakeBooksUrlPath";
[self getRequestWithURLString:urlString onSuccess:successBlock onFailure:failureBlock];
}

// central method that will handle all the get requests
- (void)getRequestWithURLString:(NSString *)urlString
onSuccess:(SuccessCompletionBlock)successBlock
onFailure:(FailureCompletionBlock)failureBlock {
// some fake implementation here to do your request, then use the completion block passed in from whatever other method
if (successBlock) {
successBlock(@"responseObjectPassedBackHere");
}
}

@end

And an example of calling it:

LLFakeManager *manager = [[LLFakeManager alloc] init];
[manager getUserInfoOnSuccess:^(id _Nonnull responseObject) {
NSLog(@"Here's my response object = %@", responseObject);
} onFailure:^(NSError * _Nonnull error) {
// no implementation but same idea
}];

Would produce this log:

Here's my response object = responseObjectPassedBackHere

This site: http://goshdarnblocksyntax.com is a handy list of block syntax which may also be helpful for you.

how to execute completion block from another method in objective-c?

It's a bit hard to understand what you are looking for, but this might be it. This is how I handle Completion callbacks in my app:

@import Foundation;

typedef void (^CompletionBlock)(NSArray *data);

@interface TheClass()

@property (nonatomic, copy) CompletionBlock *myCompletion;

@end

@implementation TheClass()

// ....


- (void) createOrderWithsuccess:(CompletionBlock)success {
_myCompletion = success;
}

-(void)listeners {
[[SocketIOManager sharedInstance].socket on:@"someAction" callback:^(NSArray* data, SocketAckEmitter* ack) {
// data received
_myCompletion(data);

}];
}

// Alternatively, breaking possible retain cycles
-(void)listeners {
__weak TheClass *weakSelf = self;
[[SocketIOManager sharedInstance].socket on:@"someAction" callback:^(NSArray* data, SocketAckEmitter* ack) {
// data received
if(weakSelf.myCompletion) weakSelf.myCompletion(data);
}];
}

The typedef should be in the .h file so that both this class and the one calling createOrderWithsuccess: knows about it.

How to know when all async functions have completed?

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 Topics



Leave a reply



Submit