3

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?

Vee
  • 1,821
  • 3
  • 36
  • 60

2 Answers2

5

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
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Actually, I just noticed that using Xcode's auto completion, I got the following: `obj.objcMethod(param1, withCompletion: { (success, result, error) -> Void in if (success){ // this worked!} }` This worked while in the accepted solution, I actually wasn't able to go into the completion block. Thoughts on why @Rob? – Vee May 25 '16 at 15:11
  • @Vee - It could be something about how the Objective-C method was implemented (the OP didn't details, so there could be some variations there). It could be the version of Xcode (I used 7.3.1). It could be many things. But, if your rendition worked, `objc.objcMethod(param1) { success, result, error in if success { ... } }` would work, too. (The version with the types explicitly declared is entirely contingent upon how the method was originally declared.) But the bottom line is that if you let auto completion do its job, you'll get a version that works for your method and environment. – Rob May 25 '16 at 22:45
3

You shouldn't specify types for the completion block parameters, as some types defer between Swift and Objective C (e.g. BOOL is actually ObjCBool in Swift). This should work:

objcMethod(param1) { (success, result, error) in
    if (success){ 
        // Do something
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Kobi
  • 266
  • 1
  • 5
  • Thanks, Kobi. Your solution worked perfectly. I accepted Rob's answer below though because he showed some more details about how Xcode code completion capability in helping me arrive at the right answer. – Vee May 24 '16 at 19:11