Return Object for a Method Inside Completion Block

Return object for a method inside completion block

If you want the MakeGetRequest method to return data obtained via dataTaskWithURL, you can't. That method performs an asynchronous call, which is most likely completed after the MakeGetRequest has already returned - but more generally it cannot be know in a deterministic way.

Usually asynchronous operations are handled via closures - rather than your method returning the data, you pass a closure to it, accepting the parameters which are returned in your version of the code - from the closure invoked at completion of dataTaskWithURL, you call that completion handler closure, providing the proper parameters:

class func MakeGetRequest(urlString: String, completionHandler: (data: NSData, error: NSError) -> Void) -> Void
{
let url = NSURL(string: urlString)
var dataResponse: NSData
var err: NSError

let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
completionHandler(data: data, error: error)
})

task.resume()
}

Swift 5 update:

class func makeGetRequest(urlString: String, completionHandler: @escaping (Data?, Error?) -> Void) -> Void {
let url = URL(string: urlString)!
var dataResponse: Data
var err: NSError

let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, respone, error) -> Void in
completionHandler(data, error)
})

task.resume()
}

Returning method object from inside block

You can't. Embrace the fact that what you're trying to do is asynchronous and add a completion block parameter to your getMyData method which is called when the inner completion handler is called. (And remove the return from the method signature):

- (void)getMyDataWithCompletion:(void(^)(NSData *data))completion {
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
completion((success ? doc.myResponseData : nil));
}];
}

The same problem exists in swift and you can add a similar completion block:

func getMyData(completion: ((data: NSData?) -> Void) {
data = ...
completion(data)
}

Get a value/object from a completion handler into an array that is outside of it

Your code looks totally fine - the reason your print statement outside of your networking call is printing 0 values is that that API call is asynchronous, so it will finish after that print statement is executed. If your array is truly being set back to an empty array then it must be happening elsewhere.

One thing you can do for debugging, possibly, is to observe that variable to see when it is being set like this:

var colors: [Color] = [] {
didSet {
//This will be called every time this array is set/changed
print(colors.count)
}
}

You could even put a breakpoint on the print statement within didSet so that you can see a trace of what is resetting your array.

Again, though, I suspect your code is fine, and you're just confused about that async completion timing.

Methods that use completion blocks and return an object

A couple of observations:

  1. I'm unclear as to why you say "because ... the return object won't be finished initialising." You're doing the initialization, so just ensure it finishes all of the associated initialization before calling that handler. If the issue is that the caller won't have a valid reference to that object yet, you could always include a reference to it in the parameter of the block, e.g.

    typedef void(^initCompletionHandler)(MyObject object, BOOL succesful);

    and then supply that parameter, e.g.:

    if (haveError){
    handler(self, NO);
    } else {
    handler(self, YES);
    }
  2. Also, you say "I obviously can't call the handler after the return". But you can. You could just do a dispatch_async, if you wanted:

    dispatch_async(dispatch_get_main_queue(), ^{
    if (haveError){
    handler(NO);
    } else {
    handler(YES);
    }
    });
    return self;

    That's a little inelegant, as if you call it from another thread, you have some potential race conditions that you might have to coordinate/synchronize, but you get the idea: You don't have to call the handler synchronously.

Having made both of those observations, I must confess that I'm not a fan of having init actually launching some asynchronous process and having its own completion block. I'd be inclined to make those two different steps. If you look at the Cocoa API, Apple has largely shifted away from this pattern themselves, generally having one method for instantiation, and another for starting the asynchronous process.

how to use the return value in a completion handler?

You're returning the value into the productToString function but not doing anything else with it.

func productToString(num: Int,num2: Int,completion: (Int)->String){

let result = num * num2

completion(result) // <--- Your return value ends up here
}

If you want to print the result you have to return it again out of the productToString function.

func productToString(num: Int,num2: Int,completion: (Int)->String) -> String {

let result = num * num2

return completion(result)
}

Sidenote: The empty brackets that are printed are an empty tuple which is equivalent to Void in Swift.

Local Object became nil before returning completion block - Objective C

I know if I use a class method or property variable I can resolve this. but is there any way to retain this object (which declared inside a function). I tried __strong and __block with the object, but not working.

Your code is trying to solve a problem which doesn't exist and it doing so creates one.

The purpose of the weakSelf/strongSelf pattern it to deal with harmful reference cycles (not all reference cycles are harmful, indeed some are useful). You should only use it if you have identified you that there is such a harmful cycle.

Let's look at your code without any weakSelf/strongSelf dance:

 1 @implementation AViewController
2 {
3 -(void)methodA
4 { LocalClass *object = [LocalClass alloc] init];
5 [object aMethodWithCompletionBlock:^(NSDictionay *result) { ... }];
6 }
7 }
8
9 @implementation LocalClass
10 {
11 - (void)aMethodWithCompletionBlock:(void (^)(NSDictionay *result))completionHandler
12 { [NetworkClass methodToMakeRESTRequestOnComplete:^(NSDictionay *someResult)
13 {
14 if (!self)
15 return;
16
17 //some code execution
18 if (completionHandler != nil)
19 completionHandler(someModifiedResult);
20 }];
21 }
22 }

Now what will happen when you have an instance of AViewController and invoke methodA?

  1. At line 3 a new instance of LocalClass is created and a reference to it stored in object. The type of object is implicitly __strong LocalClass * and so the new instance has a strong reference to it and will stay alive.
  2. At line 5 the method aMethodWithCompletionBlock: is called on the object referenced by object passing it a closure. Note that neither methodA or the instance of AViewController it is being called for keep a reference to this closure, it is just passed to the method. Therefore after the call there is no possibility of a reference cycle between a local variable belonging to methodA or an instance variable belonging to AViewController and the closure.
  3. At line 12 method methodToMakeRESTRequestOnComplete: of NetworkClass is called passing it a closure

    • This closure references self so it contains a strong reference back to the instance of LocalClass on which aMethodWithCompletionBlock: was called
    • This is the same instance of LocalClass that was created at Line 3 and so there are now two strong references to that object.
    • The closure also contains a strong reference to the block referenced by the parameter completionHandler
  4. At line 20 methodToMakeRESTRequestOnComplete: returns, as the passed block is a completion block it is unlikely to have been called yet. So at this point NetworkClass has a reference to that completion block, and the completion block has a reference to the LocalClass instance.

  5. At line 21 aMethodWithCompletionBlock: returns. The instance of LocalClass it was called on has kept no references to the parameter completionHandler.
  6. At line 6 methodA returns. This destroys its local variable object which drops the strong reference to the LocalClass instance it referenced. The system can consider destroying that instance at this point, however as NetworkClass has a strong reference to a completion block which in turn has a strong reference to this same LocalClass instance it is still needed and not destroyed.
  7. At some future time line 14 is reached after NetworkClass invokes the block reference it kept. The self variable contains a strong reference to the LocalClass instance originally created at line 4, that instance therefore still exists and all is well with the world.
  8. At line 20 the completion block returns. If at this point NetworkClass drops its strong reference to the block the block can be (probably – assuming there are no other strong references to it) destroyed. That destruction removes the strong references the block has to the objects referenced by its self and completionHandler and so those objects can also (probably...) be destroyed and the objected originally created at line 4 bites the dust.

There are no harmful cycles, no need for any weak references to manage them.

HTH

How can I return value from completion block to use it in other viewController?

queryCompletionBlock is executed asynchronously so by the time print(self.kcalUnitValid) is called queryCompletionBlock wouldn't have finished its execution yet, in other words, the order in how execution happens is like below sequentially:

  • 1.- queryOperation.queryCompletionBlock(..) is called
  • 2.- print(self.kcalUnitValid) is called // prints empty
  • 3.- after some seconds the result of queryOperation.queryCompletionBlock(..) is returned

Code

What you could do it probably is this:

func KcalCloudKitData() {
// .. code omitted
queryOperation.queryCompletionBlock = {cursor, error in
if error != nil {
// .. code omitted
} else {
for value in self.allRecords {
self.kcalUnitValid.append(value.object(forKey: "KcalValidUnit") as! [String])
}

// Segue to another ViewController
// And also sending kcalUnitValid
self.performSegue(withIdentifier: "AppLoaded", sender: self.kcalUnitValid)
}
}
// .. code omitted
}

// Actually in this method is when you decide
// what data you are going to send to the AnotherViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AppLoaded" {
let anotherViewController = segue.destination as! AnotherViewController
anotherViewController.kcalUnitValid = sender as! Array<[String]>
}
}

Resources:

Watch this video explaning async programming in Swift



Related Topics



Leave a reply



Submit