Episode #176

Asynchronous Operations

Series: Deep Dive with NSOperation

17 minutes
Published on July 3, 2015

This video is only available to subscribers. Get access to this video and 584 others.

In this episode we examine the asynchronous (a.k.a concurrent) type of NSOperation where we are doing things that involve callback blocks or delegates.

Episode Links

Creating an Asynchronous-friendly Operation Base Class

import Foundation

class Operation : NSOperation {

    override var asynchronous: Bool {
        return true
    }

    private var _executing = false {
        willSet {
            willChangeValueForKey("isExecuting")
        }
        didSet {
            didChangeValueForKey("isExecuting")
        }
    }

    override var executing: Bool {
        return _executing
    }

    private var _finished = false {
        willSet {
            willChangeValueForKey("isFinished")
        }

        didSet {
            didChangeValueForKey("isFinished")
        }
    }

    override var finished: Bool {
        return _finished
    }

    override func start() {
        _executing = true
        execute()
    }

    func execute() {
        fatalError("You must override this")
    }

    func finish() {
        _executing = false
        _finished = true
    }
}

Implementing DownloadImageOperation

class DownloadImageOperation : Operation, NSURLSessionDownloadDelegate {

    let imageURL: NSURL
    let targetPath: String

    var downloadTask: NSURLSessionDownloadTask?
    var progressBlock: (Float) -> () = { _ in }

    init(imageURL: NSURL, targetPath: String) {
        self.imageURL = imageURL
        self.targetPath = targetPath
    }

    lazy var session: NSURLSession = {
        let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
        return NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()

    override func execute() {
        if NSFileManager.defaultManager().fileExistsAtPath(targetPath) {
            NSFileManager.defaultManager().removeItemAtPath(targetPath, error: nil)
        }

        downloadTask = session.downloadTaskWithURL(imageURL)
        downloadTask?.resume()
    }

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask,
        didWriteData bytesWritten: Int64,
        totalBytesWritten: Int64,
        totalBytesExpectedToWrite: Int64) {
            let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
            dispatch_async(dispatch_get_main_queue()) {
                self.progressBlock(progress)
            }
    }

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask,
        didFinishDownloadingToURL location: NSURL) {


            let targetURL = NSURL(fileURLWithPath: targetPath)!
            var copyError : NSError?
            if NSFileManager.defaultManager().copyItemAtURL(location, toURL: targetURL,
                error: &copyError) {
                    finish()

            } else {
                fatalError("Could not copy: \(copyError)")
            }

    }
}

Running the operation from the view controller

@IBAction func downloadButtonTapped(sender: AnyObject) {

        let url = NSURL(string: "http://imgsrc.hubblesite.org/hu/db/images/hs-2006-10-a-hires_jpg.jpg")!

        let docsDir = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).first! as! String
        let targetPath = docsDir.stringByAppendingPathComponent("hubble.jpg")

        let downloadOperation = DownloadImageOperation(imageURL: url, targetPath: targetPath)

        downloadOperation.progressBlock = { self.downloadProgressView.progress = $0 }

        downloadOperation.completionBlock = {
            dispatch_async(dispatch_get_main_queue()) {
                if let image = UIImage(contentsOfFile: targetPath) {
                    self.displayImage(image)
                }
            }
        }

        operationQueue.addOperation(downloadOperation)
    }