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