Search code examples
swiftnsurlconnectionsendasynchronousrequest

NSURLConnection in Swift: How to call a function when it's finished loading, and restart it if the URL is a 404?


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.


Solution

  • 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.