objective-cswiftcompletion-block

Using Obj-C completion block in Swift


In Objective-C, I have a completion block class defined as:

File.h

typedef void (^MYCompletionBlock)(BOOL success, NSDictionary *result, NSError *error);

Then, in a Swift file, I try to use the completion block as follows:

Swift.swift

class MyClass: NSObject{
     ...

     func MyFunction() -> Void {
          ...
          objcMethod(param1, withCompletion: {(MYCompletionBlock) -> Void in
               if (success){ // Error:"Use of unresolved identifier 'success'"
               }
          }
          ... 
     }
     ...
}

But, I keep getting an error: "Use of unresolved identifier 'success'".

I've tried the following as well:

objcMethod(param1, withCompletion: {(success:Bool, result: NSDictionary, error:NSError) -> Void in
     if (success){ // Error:"Cannot convert value of type '(Bool, NSDictionary, NSError) -> Void' to expected argument type "MYCompletionBlock!" 
     }
}

Can somebody help me understand how to correctly specify a Obj-C completion block in Swift?


Solution

  • Given that your closure doesn't specify nullability qualifiers (where they almost certainly are optional), one can safely assume that your Objective-C API has not been audited for nullability. Thus, Swift will treat pointers as implicitly unwrapped optionals. Furthermore, nowadays the NSDictionary is mapped to a [NSObject : AnyObject] Swift dictionary.

    Thus, it would be:

    obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]!, error: NSError!) in
        if success {
            // do something
        }
    }
    

    Or, as Kobi points out, you can let the compiler infer the types:

    obj.objcMethod(param) { success, result, error in
        if success {
            // do something
        }
    }
    

    Note, you don't have to remember this yourself. You can leverage Xcode's code completion as you enter the code. So, type enough to match the method name and when it matches objcMethod, then hit enter:

    enter image description here

    When you get to MYCompletionBlock, hit enter again, and it will show you the correct signature:

    enter image description here


    If this Objective-C method was my own class, I would audit it for nullability. So, for example, let's assume the param is optional, the closure is required, and the result and error were optional, you might define it like so:

    NS_ASSUME_NONNULL_BEGIN
    
    typedef void (^MYCompletionBlock)(BOOL success, NSDictionary * _Nullable result, NSError * _Nullable error);
    
    @interface MyObject : NSObject
    
    - (void)objcMethod:(NSDictionary * _Nullable)param1 withCompletionHandler:(MYCompletionBlock)completionHandler;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    And, if that was the case, your Swift code would call it like so:

    obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]?, error: NSError?) in
        if success {
            // do something
        }
    }
    

    Or, again, just let the compiler infer the types for you (but this time they'd be inferred as optionals that are not implicitly unwrapped):

    obj.objcMethod(param) { success, result, error in
        if success {
            // do something
        }
    }