Storing Values in Completionhandlers - Swift

Storing values in completionHandlers - Swift

Your question is garbled. Your sample code isn't valid, and does not show a completion handler in use.

I suspect you misunderstand how async requests and completion handlers work. Here is the sequence.

  1. Call a function and provide a completion handler
  2. Function returns immediately, and at a time in the future beings running request
  3. Next line after completion handler runs before request even begun processing. Your app continues to run. No result available because the request has not completed yet.
  4. Async request finally finishes. original method invokes completion handler. NOW the result is available, in the code in the completion handler. The completion handler code does whatever is needed to handle the results.

Here is a realistic example of how an async process with completion handlers might work: (You can find a working Xcode project that demonstrates this code on Github at this link)

First a class AsyncManager that simulates downloading an image from the internet:

class AsyncManager
{
static let sharedAsyncManager = AsyncManager()

func asyncFetchImage(#imageName: String, completion: (image: UIImage?, status: String) -> ())
{
println("Entering \(__FUNCTION__)")

//Simulate a network operation by waiting 3 seconds before loading an image
let nSecDispatchTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(3.0 * Double(NSEC_PER_SEC)))
let queue = dispatch_get_main_queue()
dispatch_after(nSecDispatchTime, queue)
{
() -> Void in
let result = UIImage(named: imageName)
println("Loading image in background")
let status = result != nil ? "image loaded" : "Error loading image"
println("About to call completion handler")
completion(image: result, status: status)
}
println("Leaving \(__FUNCTION__)")
}
}

It is a singleton. It has a static let sharedAsyncManager that you use to fetch a single instance of the AsyncManager.

The AsyncManager provide a single method, asyncFetchImage, that takes an image name and a completion block. The function doesn't return any result, because it returns immediately, before the image load has taken place.

The code doesn't really download the image from the internet. Instead it simply uses the GCD call dispatch_after to wait 3 seconds before loading an image and invoking the completion block.

Now I create a ViewController class and give it a "Load Image" button. I create an IBOutlet theImageViewto a UIImageView that will display the image I'm going to load.

I write an IBAction method for the Load Image button. Here's what that IBAction looks like:

@IBAction func loadImage(sender: UIButton)
{
let theAsyncManager = AsyncManager.sharedAsyncManager
println("about to call asyncFetchImage")
theAsyncManager.asyncFetchImage(imageName: "wareto_blue_150x115")
{
(image, status) -> () in
println("Beginning completion block")
self.theImageView.image = image
println("In completion block, status = \(status)")
}
println("After call to asyncFetchImage")
}
}

Now, the fun part. Here's the sequence of events:

I press the loadImage button. The IBAction method runs.

It gets a reference to the async download manager singleton.

It displays a message, then calls theAsyncManager.asyncFetchImage. The AsyncManager.asyncFetchImage method displays a message on entry, queues up a call to load the image 3 seconds later, displays a "leaving" message, and returns, before the image is loaded. There is nothing to return, because the image loading code hasn't run yet.

The view controller's loadImage method displays it's "After call to asyncFetchImage" message and returns.

A few seconds later, the code inside asyncFetchImage actually runs. It displays a message, loads the image, then invokes the completion handler.

Here is the set of messages you get back when you run the code above:

about to call asyncFetchImage
Entering asyncFetchImage(imageName:completion:)
Leaving asyncFetchImage(imageName:completion:)
After call to asyncFetchImage
Loading image in background
About to call completion handler
Beginning completion block
In completion block, status = image loaded

Note that the last line of the loadImage IBAction:

println("After call to asyncFetchImage")

displays a message before the message about loading an image is displayed. The code call to asyncFetchImage returns immediately, before any work is done. The code after the call to asyncFetchImage runs next, but the image still hasn't loaded. There's no way to return a result at this point, because the image load is hasn't even started yet.

How to store a Closure completion handler to call later?

Yes. It will work as expected.

Whenever you call requestProductsWithCompletionHandler you create a closure and pass it to that function.
And as you mentioned, when you set completionHandler you actually set it to be a reference to the given closure.

In Objective C, to store the block as ivar you had to copy a block. That's because a block first residing on the stack memory where it was defined. And copy moved it to the heap memory so it can be used

Store a closure as a variable in Swift

The compiler complains on

var completionHandler: (Float)->Void = {}

because the right-hand side is not a closure of the appropriate signature, i.e. a closure taking
a float argument. The following would assign a "do nothing" closure to the
completion handler:

var completionHandler: (Float)->Void = {
(arg: Float) -> Void in
}

and this can be shortened to

var completionHandler: (Float)->Void = { arg in }

due to the automatic type inference.

But what you probably want is that the completion handler is initialized to nil
in the same way that an Objective-C instance variable is inititialized to nil. In Swift
this can be realized with an optional:

var completionHandler: ((Float)->Void)?

Now the property is automatically initialized to nil ("no value").
In Swift you would use optional binding to check of a the
completion handler has a value

if let handler = completionHandler {
handler(result)
}

or optional chaining:

completionHandler?(result)

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.

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.

Swift completion handler

var completionHandlers: [() -> Void] = []

for i in 1...10 {

let handler = {

print(i)

}

completionHandlers.append(handler)

}

After that you can execute the handlers:

completionHandlers[0]()

or, to make the code more expressive:

let handler = completionHandlers[0]
handler()

And to go through all of the handlers:

completionHandlers.forEach { handler in
handler()
}

Be careful, when you are storing the handler in an array, you have a strong reference to it. Because the closure keeps a strong reference to everything you are referring to within the closure, it's easy to create a reference cycle when you refer to self:

let handler = {
self.myFunction()
}

You can avoid the retaicnycle by making a weak reference to self:

let handler = { [weak self] in
self?.myFunction()
}

Where to put the completionHandler in my function Swift 3

As you can see, if you pass 6 as the first argument, your closure will execute twice:

  1. When you get the true in the condition block;
  2. When the closure is called as an escaping one.

As an example, I wrote a one-method class, which demonstrates a closure's behavior by printing the passed argument as result.

You can see the results below
Sample Image

How to use completionHandler Closure with return in Swift?

func getSomething(callback: (Array<AnyObject>) -> ()) {
var dataTask = NSURLSessionDataTask()
dataTask = session.dataTaskWithRequest(request) { (data, response, error) in
if (error == nil) {
var callbackArray = Array<MyObject>()
let responseDict = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSDictionary
let response = responseDict.objectForKey("response_key") as NSDictionary
let array = response.objectForKey("array_key") as NSArray

for item: AnyObject in array {
var arrayItem = MyObject(dict: item as NSDictionary)
callbackArray.append(arrayItem)
}

callback(callbackArray)
} else {
// handle an error
}
}
dataTask.resume()
}

Then you could do something like:

getSomething() { (response) in
if let responseArray = response as? Array<MyObject> {
self.somethings = responseArray
}
}

Swift @escaping and Completion Handler

Swift Completion Handler Escaping & Non-Escaping:

As Bob Lee explains in his blog post Completion Handlers in Swift with Bob:

Assume the user is updating an app while using it. You definitely want
to notify the user when it is done. You possibly want to pop up a box
that says, “Congratulations, now, you may fully enjoy!”

So, how do you run a block of code only after the download has been
completed? Further, how do you animate certain objects only after a
view controller has been moved to the next? Well, we are going to find
out how to design one like a boss.

Based on my expansive vocabulary list, completion handlers stand for

Do stuff when things have been done

Bob’s post provides clarity about completion handlers (from a developer point of view it exactly defines what we need to understand).

@escaping closures:

When one passes a closure in function arguments, using it after the function’s body gets executed and returns the compiler back. When the function ends, the scope of the passed closure exist and have existence in memory, till the closure gets executed.

There are several ways to escaping the closure in containing function:

  • Storage: When you need to store the closure in the global variable, property or any other storage that exist in the memory past of the calling function get executed and return the compiler back.

  • Asynchronous execution: When you are executing the closure asynchronously on despatch queue, the queue will hold the closure in memory for you, can be used in future. In this case you have no idea when the closure will get executed.

When you try to use the closure in these scenarios the Swift compiler will show the error:

error screenshot

For more clarity about this topic you can check out this post on Medium.

Adding one more points , which every ios developer needs to understand :

  1. Escaping Closure : An escaping closure is a closure that’s called after the function it was passed to returns. In other words,
    it outlives the function it was passed to.
  2. Non-escaping closure : A closure that’s called within the function it was passed into, i.e. before it returns.


Related Topics



Leave a reply



Submit