Objective-C callback handler

58,267

I'm not entirely sure what you're trying to do there - your callback is a block... is that intentional? I would expect your method to look something like this:

- (void)signInAccountWithUserName:(NSString *)userName password:(NSString *)password;

If the intention of your callback is to execute some additional code (specified when you call the method) on completion, then a block would be useful. For example, your method would look like this:

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(void))completionBlock
{
    // ...
    // Log into the account with `userName` and `password`...
    //

    if (successful) {
        completionBlock();
    }
}

And then call the method like so:

[self signInAccountWithUserName:@"Bob"
                       password:@"BobsPassword"
                     completion:^{
                         [self displayBalance];  // For example...
                     }];

This method call would log the user into the account and then as soon as that is complete, show the balance. This is clearly a contrived example, but hopefully you get the idea.

If this is not the kind of thing you intended, then simply use a method signature like the one above.


EDIT (A better example using the successful variable):

A better design would be to pass a Boolean back in the completion block that describes how well the login went:

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(BOOL success))completionBlock
{
    // Log into the account with `userName` and `password`...
    // BOOL loginSuccessful = [LoginManager contrivedLoginMethod];

    // Notice that we are passing a BOOL back to the completion block.
    if (completionBlock != nil) completionBlock(loginSuccessful);
}

You'll also see that this time around we're checking that the completionBlock parameter is not nil before calling it - this is important if you want to allow the method to be used without a completion block. You might use this method like so:

[self signInAccountWithUserName:@"Bob"
                       password:@"BobsPassword"
                     completion:^(BOOL success) {
                         if (success) {
                             [self displayBalance];
                         } else {
                             // Could not log in. Display alert to user.
                         }
                     }];

Better still (if you can excuse the swaths of examples!), if it would be useful for the user to know the reason for the failure, return an NSError object:

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(NSError *error))completionBlock
{
    // Attempt to log into the account with `userName` and `password`...

    if (loginSuccessful) {
        // Login went ok. Call the completion block with no error object.
        if (completionBlock != nil) completionBlock(nil);
    } else {
        // Create an error object. (N.B. `userInfo` can contain lots of handy 
        // things! Check out the NSError Class Reference for details...)
        NSInteger errorCode;
        if (passwordIncorrect) {
            errorCode = kPasswordIncorrectErrorCode;
        } else {
            errorCode = kUnknownErrorCode;
        }
        NSError *error = [NSError errorWithDomain:MyLoginErrorDomain code:errorCode userInfo:nil];
        if (completionBlock != nil) completionBlock(error);
    }
}

The caller can then make use of the NSError in the completion block to decide how to proceed (most likely, to describe to the user what went wrong). This kind of pattern is slightly less common (though perfectly valid); mostly NSErrors are returned by pointer indirection, for example in the NSFileWrappers -initWithURL:options:error: method:

NSError *error;
NSFileWrapper *fw = [[NSFileWrapper alloc] initWithURL:url options:0 error:&error];
// After the above method has been called, `error` is either `nil` (if all went well),
// or non-`nil` (if something went wrong).

In the login example, however, we are probably expecting the login attempt to take some amount of time to complete (for example logging into an online account), so it is perfectly reasonable to make use of a completion handler that passes an error back.

Share:
58,267
Jesse
Author by

Jesse

Updated on June 28, 2020

Comments

  • Jesse
    Jesse almost 4 years

    I have a callback method that I got to work, but I want to know how to pass values to it.

    What I have is this:

    @interface DataAccessor : NSObject
    {
        void (^_completionHandler)(Account *someParameter);
    
    }
    
    
    - (void) signInAccount:(void(^)(Account *))handler;
    

    The code above works, but I want to pass values to the method. How would this look? Something like:

    - (void) signInAccount:(void(^)(Account *))handler user:(NSString *) userName pass:(NSString *) passWord;
    

    ?

  • Stuart
    Stuart over 12 years
    @vikingosegundo: Yes, good suggestion. Although, depending on the context it might be easier to return that success as a BOOL, or possibly pass in an NSError object by pointer-indirection to determine how well the sign-in went.
  • sixstatesaway
    sixstatesaway over 11 years
    Could someone please explain what the if (successful) part is about? How do I define successful? I assume it's a BOOL?
  • Stuart
    Stuart over 11 years
    @pigeonfactory: In the case of the example above, successful is just an assumed local variable that is returned to describe whether the login worked or not. A better solution might be return that Boolean in the completion block. I have added an edit to the answer that illustrates this.
  • iOS Developer
    iOS Developer about 6 years
    @Stuart would you do something like completion handler stackoverflow.com/questions/49857236/…
  • ZAFAR007
    ZAFAR007 over 5 years
    Swift func myFunction(str: String, completionHandler: @escaping (String) -> ()){ completionHandler("") } myFunction(str: "someThing", completionHandler: {(str) in })