In this episode we discuss dependent NSOperations. Using dependencies you can queue up a bunch of jobs and they will be run in the correct order, having one provide the necessary state for the next one to run. In the example, we take the large Hubble image, scale it down to a more appropriate size, then apply a Core Image filter to it. Each operation is dependent on the one before it, yet they are all queued up at the same time.
Episode Links Source Code Creating a resize operation class ResizeImageOperation : Operation { let path: String let containingSize: CGSize init(path: String, containingSize: CGSize) { self.path = path self.containingSize = containingSize } override func execute() { let sourceImage = UIImage(contentsOfFile: path)! println("Source image size: \(sourceImage.size)") printSizeOnDisk(UIImageJPEGRepresentation(sourceImage, 1.0)) let width: CGFloat let height: CGFloat let ratio = sourceImage.size.width / sourceImage.size.height if sourceImage.size.width >= sourceImage.size.height { width = containingSize.width height = width / ratio } else { height = containingSize.height width = height * ratio } let imageSize = CGSize(width: width, height: height) println("Resized image: \(imageSize)") UIGraphicsBeginImageContextWithOptions(imageSize, true, 0.0) let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize) sourceImage.drawInRect(rect) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() let imageData = UIImageJPEGRepresentation(resizedImage, 0.8) printSizeOnDisk(imageData) imageData.writeToFile(path, atomically: true) finish() } func printSizeOnDisk(data: NSData) { let bytes = Int64(data.length) let size = NSByteCountFormatter.stringFromByteCount(bytes, countStyle: NSByteCountFormatterCountStyle.File) println("Size on disk: \(size)") } } Creating a filter operation class FilterImageOperation : Operation { let path: String var outputImage: CIImage? init(path: String) { self.path = path } lazy var inputImage: CIImage = { let url = NSURL(fileURLWithPath: self.path)! return CIImage(contentsOfURL: url) }() lazy var filter: CIFilter = { return CIFilter(name: "CISepiaTone", withInputParameters: [ kCIInputImageKey : self.inputImage ]) }() override func execute() { outputImage = filter.outputImage finish() } } Setting up the dependencies @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 } var size = CGSize(width: imageView.bounds.size.width * 2, height: imageView.bounds.size.height * 2) let resizeOperation = ResizeImageOperation(path: targetPath, containingSize: size) resizeOperation.addDependency(downloadOperation) let filterOperation = FilterImageOperation(path: targetPath) filterOperation.addDependency(resizeOperation) // Thanks to Derek in the comments for pointing out the retain cycle here. Updated to use a weak reference. filterOperation.completionBlock = { [weak filterOperation] in if let outputImage = filterOperation?.outputImage { let context = CIContext(options: [:]) let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent()) let image = UIImage(CGImage: cgImage)! self.displayImage(image) } } operationQueue.suspended = true operationQueue.addOperation(downloadOperation) operationQueue.addOperation(resizeOperation) operationQueue.addOperation(filterOperation) operationQueue.suspended = false }