5

I am creating a completionHandler which returns a Dictionary but when I call this method in another class the value of it is nil.

func fetchLatestPosts(completionHandler: (responseDict: NSDictionary) -> Void in {
// Bunch of JSON Parsing for dictionary to be completed
var theDictionary = JSON Dictionary value
responseDict = theDictionary as NSDictionary
}

Then I am attempting to call it in another class so that the value that fetchLatestPost Dictionary can be viewed and data presented based on that. For Example,

func parseDictionary() {
NetworkManager.sharedInstance.fetchLatestPosts(responseDict: NSDictionary)
if (responseDict != nil) {
println("Dictionary is not empty")
}

The issue here is that when I am calling the fetchLatestPosts function I am receiving a nil value when attempting to call the Dictionary.

In short, my question is how can I assign values to the completion handler are stored and can be called elsewhere within the Xcode project?

1 Answers1

6

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.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • You still don't understand how completion blocks work. – Duncan C May 14 '15 at 11:22
  • most likely yeah, i'm new to programming sorry. –  May 14 '15 at 12:33
  • See my edits. I added code from a working example that show how completion handlers actually work. – Duncan C May 14 '15 at 13:38
  • @Jack, I created a demo project on Github called [**SwiftCompletionHandlers**](https://github.com/DuncanMC/SwiftCompletionHandlers.git) that illustrates how completion handlers work. The code in my edit is taken from that project. I suggest you download it and run it. Add breakpoints if you don't understand how the flow of control works. – Duncan C May 14 '15 at 15:48
  • Hey Duncan, I really appreciate the demo project you sent me and it helped me to understand what I was attempting to ask. My original question was regarding the part where the values are stored in the completion handler during it's asynchronous call, which in your example, was setting the values of `result` and `status` to `image` and `status` `completion(image: result, status: status)`. I now understand it so big thanks for your help! –  May 16 '15 at 10:54
  • Point of clarification: The values are not "stored in the completion handler". I defined my completion handler to take parameters. The closure gets invoked with those parameters. – Duncan C May 17 '15 at 12:44
  • ok but if I call the parameters when I call the function in another class the parameter values will be able to be accessed within the other class, correct? –  May 18 '15 at 01:49