Difference Between Completion Handler and Blocks:[Ios]

How does a completion handler work on iOS?

Basically in this case it works like that:

  1. You call fetchUsersWithCompletionHandler: from whatever thread you like (probably form main one).
  2. It initializes NSURLRequest, then calls:
    dispatch_async(dispatch_get_global_queue...
    which basically creates block, and schedules it for processing on a background queue.
  3. Since it is dispath_async, current thread leaves the fetchUsersWithCompletionHandler: method.

    ...

    time passes, till background queue has some free resources

    ...

  4. And now, when the background queue is free, it consumes scheduled operation (Note: It performs synchronous request - so it waits for data):

    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
    returningResponse:&response
    error:&error];
    ...
  5. Once data comes, then the usersArray is populated.

  6. Code continues to this part:

    if (handler){
    dispatch_sync(dispatch_get_main_queue(), ^{
    handler(usersArray);
    });
    }
  7. Now, if we have handler specified, it schedules block for invocation on a main queue. It is dispatch_sync, so execution on current thread won't proceed till main thread will be done with the block. At this point, background thread patiently waits.

    ...

    another moment passes

    ...

  8. Now main queue has some free resources, so it consumes above block, and executes this code (passing previously populated usersArray to the 'handler'):

    handler(usersArray);
  9. Once it is done, it returns from the block and continues consuming whatever it is in the main queue.

  10. Since main thread is done with the block, also background thread (stuck at dispatch_sync) can proceed further. In this case it simply returns from block.

Edit:
As for the questions you asked:

  1. It's not like main/background queue will be always busy, it's just it may be. (assuming background queue does not support concurrent operations like the main one). Imagine following code, that is being executed on a main thread:

        dispatch_async(dispatch_get_main_queue(), ^{
    //here task #1 that takes 10 seconds to run
    NSLog(@"Task #1 finished");
    });
    NSLog(@"Task #1 scheduled");

    dispatch_async(dispatch_get_main_queue(), ^{
    //here task #2 that takes 5s to run
    NSLog(@"Task #2 finished");
    });
    NSLog(@"Task #2 scheduled");

Since both are dispatch_async calls, you schedule these for execution one after another. But task #2 won't be processed by main queue immediately, since first it has to leave current execution loop, secondly, it has to first finish task #1.

So the log output will be like that:

Task #1 scheduled
Task #2 scheduled
Task #1 finished
Task #2 finished

2.You have:

typedef void (^Handler)(NSArray *users);

Which declares block typedefe'd as Handler that has void return type and that accepts NSArray * as parameter.

Later, you have your function:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

Which takes as a parameter block of type Handler and allow access to it using local name handler.

And step #8:

handler(usersArray);

Which just directly calls handler block (like you were calling any C/C++ function) and passes usersArray as a parameter to it.

Using Completion Blocks, versus returning a variable - iOS Swift

The concept of using completion handlers is fine. The whole point of the completion handlers is that they are meant to allow a function to execute asynchronously while providing some kind of callback to fire later when the function has finished. It's not a question of whether or not you are using completion handlers too frequently. It's a question of how frequently you are performing asynchronous tasks. Your use of completion handlers will usually be directly correlative to the number of tasks your program executes which need to be done asynchronously.

Just as an example, there are a few places where you probably want to use asynchronous completion handlers in an iOS app:

UI animation (e.g. UIView.animate(withDuration:animations:) )

Network calls (e.g. URLRequest or Data(contentsOf:options:) )

CoreData operations (e.g. NSManagedObjectContext.fetch(_:) ) though often even this is fast enough to perform synchronously without a completion handler

and more

Anything which has a processing time long enough to "freeze" the UI for a user in a way noticeable by humans.

Your example function is definitely a case where a simple return value should suffice.

func fetchObject(name: String!, completionHandler: @escaping (_ success: Array<String>?) -> Void) {
let objArray = ["one", "two", "three", name] //NOTE THIS IS A SILLY FUNCTION EXAMPLE BUT GETS THE STRUCTURE POINT ACROSS
completionHandler(objArray)
}

could be converted to:

func fetchObject(name: String!) -> Array<String> {
let objArray = ["one", "two", "three", name] //NOTE THIS IS A SILLY FUNCTION EXAMPLE BUT GETS THE STRUCTURE POINT ACROSS
return objArray
}

These two functions result in exactly the same thing. They are just implemented slightly differently. A case where a completion handler would make (theoretical) sense would be if the function looked instead like this:

func fetchObject(name: String!, completionHandler: @escaping (_ success: Array<String>?) -> Void) {
DispatchQueue.main.async(execute: { () -> Void in
let objArray = ["one", "two", "three", name] //NOTE THIS IS A SILLY FUNCTION EXAMPLE BUT GETS THE STRUCTURE POINT ACROSS
completionHandler(objArray)
})
}

Because the function uses a different thread, the use of a completion handler makes more sense since the function is no longer synchronous. So where before this would have been valid:

var result: Array<String> = Array<String>()
fetchObject(name: "name", completionHandler: { (otherResult) -> Void in
result = otherResult ?? []
})
let first = result.first //first contains "one" when function is synchronous
//first contains nil when function is asynchronous (DispatchQueue)

If we perform the body of the function asynchronously then this would no longer be valid since let first = result.first will be executed before result is ever successfully assigned to otherResult.

Of course keep in mind that in the end it is entirely up to you. The benefit of completion handlers is their ability to execute asynchronously. In some cases if they are functionally unnecessary then it just adds unneeded verbosity. It does come up to personal preference, though, since clearly they can both be implemented to produce the exact same result.

Let me know if this makes sense. I can elaborate further upon request.

Completion handlers and return values

You need to change getCurrentClient to take in a completion block instead of returning a value.

For example:

-(void)getCurrentClientWithCompletionHandler:(void (^)(NSDictionary* currentClient))handler
{
NXOAuth2Account *currentAccount = [[[NXOAuth2AccountStore sharedStore] accounts] lastObject];

[NXOAuth2Request performMethod:@"GET"
onResource:[NSURL URLWithString:[NSString stringWithFormat:@"%@/clients/%@", kCatapultHost, currentAccount.userData[@"account_name"]]]
usingParameters:nil
withAccount:currentAccount
sendProgressHandler:nil
responseHandler:^ (NSURLResponse *response, NSData *responseData, NSError *error) {
NSError *jsonError;

NSDictionary* deserializedDict = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&jsonError];
handler(deserializedDict);
}];
}

It's important to remember that getCurrentClient will return immediately, while the network request is dispatched on another thread. Don't forget that if you want to update the UI using your response handler, you need to have your handler run on the main thread.

enumerateObjectsUsingBlock and completion together

To queue up execution of a series of a blocks, you can simply start the new block in the completion handler of the old block:

NSEnumerator *objectEnum = [arrPendingQueue objectEnumerator];
__block void (^handler) ();
handler = ^(UIImage *image)
{
// Handle image
object = [objectEnum nextObject];
if( object == nil )
{
// completion of all operations
}
else
{
// Start next operation
[self generateMapImage:object.postObj completion:handler];
}
};
[self generateMapImage:obj.postObj completion:handler];
}

A more classical or elegant way would be to use a Y-combinator. But it is not necessary. Just keep in mind, that local scope variables are cleaned up when losing their scope. handler is not retained. So maybe you have to put it into an ivar.

However, I see -generateMapImage:completion is written by you. I cannot see the content, but if you do a async call there and you can set the queue used for the call, simply set it to a serial queue. In such a case your requests will be executed serial automatically.

How to write a completion handler in a separated block of code in Swift with parameters out of scope

Ok based on our comments above, this is the route I would try: Write a short completion handler that calls your longer completion handler, passing the variables that are out of scope.

let task = URLSession.shared.dataTask(with: requestImagemGrafica) { data, response, error in

myCompletionHandler(data, response, error, marca, myUploadGroupMarcas)

}

Then you add two parameters to your completion handler in the function definition:

let myCompletionHandler: (Data?, URLResponse?, Error?, MarcaClass, myUploadGroupMarcas) -> Void

Obviously you need to replace MarcaClass with the actual class type that is marca and myUploadGroupMarcas seems to be a function so you'd need to write an appropriate parameter type for that.

Difference between return of Any vs Void in Async Block

Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.

I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet

OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).

You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:

dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}

In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.



Related Topics



Leave a reply



Submit