0

I am using Google Image API to generate images like this:

let url = NSURL(string: "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=seattle")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ (response, go, error) -> Void in            
let go = NSJSONSerialization.JSONObjectWithData(go, options: NSJSONReadingOptions.AllowFragments, error: nil) as [String:AnyObject]
let responseData = go["responseData"] as [String:AnyObject]

Then the rest of code digs down to find the URL of the image google api gives and sets (var theurl here) the image in the app to that:

let data = NSData(contentsOfURL: theurl!)
self.mainImage.image = UIImage(data: data!)

This works but I need 1) to stop the process and call a function if it finds an image URL that it can't load and 2) call a function when the image has finished loading.

ZhouW
  • 1,187
  • 3
  • 16
  • 39

2 Answers2

3

You have a few issues here that need to be addressed. I think it will make more sense to walk through an example. The following is your exact same logic, just slightly more robust.

import UIKit

class ImageViewController: UIViewController {
    var mainImage: UIImageView!
    let operationQueue: NSOperationQueue = {
        let queue = NSOperationQueue()
        queue.maxConcurrentOperationCount = 4
        queue.qualityOfService = NSQualityOfService.Utility

        return queue
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        downloadImage { [weak self] image in
            if let strongSelf = self {
                if let image = image {
                    strongSelf.mainImage.image = image
                }
            }
        }
    }

    func downloadImage(completion: UIImage? -> Void) {
        let url = NSURL(string: "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=seattle")
        let request = NSURLRequest(URL: url!)

        NSURLConnection.sendAsynchronousRequest(request, queue: self.operationQueue) { [weak self] response, data, error in
            if let strongSelf = self {
                if error != nil || data == nil {
                    println(error) // failed to complete original google api request
                    completion(nil)
                    return
                }

                var serializationError: NSError?
                if let go = NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments, error: &serializationError) as? [String: AnyObject] {
                    let responseData = go["responseData"] as [String:AnyObject]
                    let imageURL = NSURL(string: "some_image_url_from_response_data")! // placeholder
                    let imageRequest = NSURLRequest(URL: url!)

                    NSURLConnection.sendAsynchronousRequest(imageRequest, queue: strongSelf.operationQueue) { response, data, error in
                        if error != nil || data == nil {
                            println(error) // failed to download the image
                            completion(nil)
                            return
                        }

                        if let image = UIImage(data: data!) {
                            completion(image)
                        } else {
                            println("Failed to create UIImage from downloaded image data")
                            completion(nil)
                        }
                    }
                } else {
                    println(serializationError)
                    completion(nil)
                }
            }
        }
    }
}

Asynchronous Operation Queue

First off, you were making an asynchronous request on the main queue which defeats the whole purpose of async behavior. Instead, I made a concurrent operation queue property which is used to handle the download operations.

Weakify / Strongify

Anytime you are doing asynchronous things, you have to be VERY careful about retaining self. The safest way to guarantee you always do it properly is to weakify / strongify to avoid creating a retain cycle.

Nested Async Request

You should use another sendAsynchronousRequest call to download the image in the background. Otherwise you will block the main thread while you download the image. Your users would not be happy with that behavior!

Safe Deserialization

It's pretty important to check that your JSON parsing is successful. Servers can do some odd things sometimes, and your code should be able to handle that without any issues.

Alamofire

Have you taken a look at Alamofire? It makes things like this much MUCH easier.

Hopefully that helps shed some light.

cnoon
  • 16,575
  • 7
  • 58
  • 66
1

Use the completion-block and check the NSError you get.

NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
    if let theError = error{
       if theError.code == 404{
        //Image not available
       }else{
        //something else
       }
    }
})
Christian
  • 22,585
  • 9
  • 80
  • 106
  • Do I copy and paste that while also keeping my original "NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ (response, go, error) -> Void in" line? Also with the queue: queue I get a "use of unresolved identifier 'queue'" error, should I put "var queue: NSOperationQueue = NSOperationQueue()" at the top and then do queue: self.queue? – ZhouW Mar 17 '15 at 15:17
  • That is a sample Code. You have to edit it that it works for you. But you need to use that instead of your method. – Christian Mar 17 '15 at 15:19
  • Note that this will crash if no error occurs since error will be nil. – David Berry Mar 17 '15 at 16:35