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:
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);
}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
?
- At line 3 a new instance of
LocalClass
is created and a reference to it stored inobject
. The type ofobject
is implicitly__strong LocalClass *
and so the new instance has a strong reference to it and will stay alive. - At line 5 the method
aMethodWithCompletionBlock:
is called on the object referenced byobject
passing it a closure. Note that neithermethodA
or the instance ofAViewController
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 tomethodA
or an instance variable belonging toAViewController
and the closure. At line 12 method
methodToMakeRESTRequestOnComplete:
ofNetworkClass
is called passing it a closure- This closure references
self
so it contains a strong reference back to the instance ofLocalClass
on whichaMethodWithCompletionBlock:
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
- This closure references
At line 20
methodToMakeRESTRequestOnComplete:
returns, as the passed block is a completion block it is unlikely to have been called yet. So at this pointNetworkClass
has a reference to that completion block, and the completion block has a reference to theLocalClass
instance.- At line 21
aMethodWithCompletionBlock:
returns. The instance ofLocalClass
it was called on has kept no references to the parametercompletionHandler
. - At line 6
methodA
returns. This destroys its local variableobject
which drops the strong reference to theLocalClass
instance it referenced. The system can consider destroying that instance at this point, however asNetworkClass
has a strong reference to a completion block which in turn has a strong reference to this sameLocalClass
instance it is still needed and not destroyed. - At some future time line 14 is reached after
NetworkClass
invokes the block reference it kept. Theself
variable contains a strong reference to theLocalClass
instance originally created at line 4, that instance therefore still exists and all is well with the world. - 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 itsself
andcompletionHandler
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
Remove Cell When Button Pressed Inside Cell Customtableviewcell
Convert Data to Dispatchdata in Swift 4
Nssavepannel - How to Restrict User to Only Save One One Set Directory
Creating Decoration View as Custom Column in UIcollection View
Alamofire Nonblocking Connection
How to Access Camera with Swiping Gesture
How to Use Frame to Set X Position in Loop on Swift
Preparing for Swift 4 - Unsafemutablepointer Migration to Unsafemutablebufferpointer
Swift Framework with Opencv Dynamic Framework, Library Not Loaded
Nsurlsession Upload File to Server Swift
Transparent Sticky Header UI Collectionview Don't Show Cells Underneath
Given a Swift 'Any' Type How to Determine If It's an 'Optional'
Skipnext Skipprevious Google Cast Greyed Out
Connecting Avaudiosourcenode to Avaudiosinknode Does Not Work