Muhammad Umar Muhammad Umar - 6 months ago 32
Objective-C Question

Conversion of Block with callback in Objective C to closure in Swift

I have following callback in block in Objective C

typedef void (^COMPLETION_BLOCK)(NSString *response, NSError *errorString);

+ (void)responseFromURL:(NSURL *)url completionBlock:(COMPLETION_BLOCK)completionBlock
{
__block NSError *error = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSString *response = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];

dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(response, error);
});
});
}


Here is how i call it in Objective C

[Utilities responseFromURL:NSURL URLWithString:@""] completionBlock:^(NSString *response, NSError *errorString)
{

}]


I have tried converting this in Clousers in swift
here is the code

class Utilities
{
typealias COMPLETION_BLOCK = ( response : NSString, error : NSError) -> ()

func responseFromURL(url: NSURL , completionBlock:COMPLETION_BLOCK)
{
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
var e : NSError?;

dispatch_async(dispatch_get_global_queue(priority, 0))
{
var content:NSString? = NSString(contentsOfURL: url,
encoding: NSUTF8StringEncoding,
error: &e)

dispatch_async(dispatch_get_main_queue())
{
completionBlock(response: content!,error: e!);
}
}
}
}


I am not sure if this is correct conversion. Secondly how am i suppose to call the block in swift?

var urlString = String(format: BASE_URL + "%d", categoryType);
var url = NSURL(string :urlString);

Answer

You are almost there. You don't need the typealias as you can specify the completionBlock in the function definition.

One thing that would cause you problems is the forced unwrapping of content & e one of which is going to be nil.

Here is my version of your function:

func responseFromURL(url : NSURL, completionBlock : (response : String?, error : NSError?) -> Void) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        var error : NSError? = nil
        let response = String(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: &error)

        dispatch_async(dispatch_get_main_queue()) {
            completionBlock(response: response, error: error)
        }
    }
}

And to call it:

responseFromURL(myURL) { (response, error) in
    if error != nil {
        // handle error
    }
    else if response != nil {
        // data came back
    }
}

A couple of points to notice:

  • in the completion block definition, I specified that both response and error parameters are optionals. You should get one or the other back, but you need to check which one.
  • unlike in Objective-C, you do not have to send in a pointer to the error. The completion handler will send you back an error if it needs to.
  • I used the shorter form of Swift closures but if you let auto-complete do its thing, you will get the longer form which may be more readable.
Comments