Skip to content

kean/Nuke

Repository files navigation

Advanced Swift framework for loading, processing, caching and preheating images.

To get started check out http://kean.github.io/Nuke/ !

var request = ImageRequest(URL: NSURL(string: "http://..."))
request.targetSize = CGSize(width: 200, height: 200) // Resize image
request.processor = ImageFilterGaussianBlur() // Apply image filter

Nuke.taskWith(request) { response in
    let image = response.image
}.resume()

Features

Loading
  • Uses NSURLSession with HTTP/2 support
  • A single data task is used for multiple equivalent requests
  • Automated preheating of images close to the viewport
  • Congestion control that prevents trashing the system with requests during fast scrolling
  • Full featured extensions for UI components
Caching
  • Doesn't reinvent caching, relies on HTTP cache in URL Loading System
  • Two cache layers including auto purging memory cache
  • Intellegent control over memory cache
Processing
  • Create, compose and apply image filters
  • Core Image integration
  • Background image decompression and scaling in a single step
  • Resize loaded images to fit displayed size
  • iOS 8.0+ / watchOS 2.0+ / OS X 10.9+ / tvOS 9.0+
  • Xcode 7.1+, Swift 2.0+

Getting Started

Documentation

Usage

Creating Image Task

Loading an image is as simple as creating and resuming an ImageTask. Nuke is thread safe, you can freely create and resume tasks from any thread. The completion closure is called on the main thread.

Nuke.taskWith(NSURL(URL: "http://...")!) {
    let image = $0.image
}.resume()

Adding Request Options

Each ImageTask object is created with an ImageRequest struct which contains request parameters. An ImageRequest itself can be initialized either with NSURL or NSURLRequest.

var request = ImageRequest(URLRequest: NSURLRequest(NSURL(URL: "http://...")!))

// Set target size (in pixels) and content mode that describe how to resize loaded image
request.targetSize = CGSize(width: 300.0, height: 400.0)
request.contentMode = .AspectFill

// Set filter (more on filters later)
request.processor = ImageFilterGaussianBlur()

// Control memory caching
request.memoryCacheStorageAllowed = true // true is default
request.memoryCachePolicy = .ReloadIgnoringCachedImage // Force reload

// Change the priority of the underlying NSURLSessionTask
request.priority = NSURLSessionTaskPriorityHigh

Nuke.taskWith(request) {
    // - Image is resized to fill target size
    // - Blur filter is applied
    let image = $0.image
}.resume()

Processed images are stored into memory cache for fast access. Next time you start the equivalent request the completion closure will be called synchronously.

Using Image Response

The response passed into the completion closure is represented by an ImageResponse enum. It has two states: Success and Failure. Each state has some values associated with it.

Nuke.taskWith(request) { response in
    switch response {
    case let .Success(image, info):
        // Use image and inspect info
        if (info.isFastResponse) {
            // Image was returned from the memory cache
        }
    case let .Failure(error):
        // Handle error
    }
}.resume()

Using Image Task

ImageTask is your primary interface for controlling the image load. Task is always in one of four states: Suspended, Running, Cancelled or Completed. The task is always created in a Suspended state. You can use the corresponding resume(), suspend() and cancel() methods to control the task's state. It's always safe to call these methods, no matter in which state the task is currently in.

let task = Nuke.taskWith(imageURL).resume()
print(task.state) // Prints "Running"

// Cancels the image load, task completes with an error ImageManagerErrorCode.Cancelled
task.cancel()

You can also use ImageTask to monitor load progress.

let task = Nuke.taskWith(imageURL).resume()
print(task.progress) // The initial progress is (completed: 0, total: 0)

// Add progress handler which gets called periodically on the main thread
task.progressHandler = { progress in
   // Update progress
}

// Task represents an image promise
// It allows you to add multiple completion handlers, even when the task is completed
task.completion {
    let image = $0.image
}

Using UI Extensions

Nuke provides full-featured UI extensions to make image loading as simple as possible.

let imageView = UIImageView()

// Loads and displays an image for the given URL
// Previously started requests are cancelled
// Uses ImageContentMode.AspectFill and current view size as a target size
let task = imageView.nk_setImageWith(NSURL(URL: "http://...")!)
// let task = imageView.nk_setImageWith(ImageRequest(...))

You have some extra control over loading via ImageViewLoadingOptions. If allows you to provide custom animations, or completely override the completion handler.

let imageView = UIImageView()
let request = ImageRequest(URLRequest: NSURLRequest(NSURL(URL: "http://...")!))

var options = ImageViewLoadingOptions()
options.handler = {
    // The `ImageViewLoading` protocol controls the task
    // You handle its completion
}
let task = imageView.nk_setImageWith(request, options: )

Adding UI Extensions

Nuke makes it extremely easy to add image loading extensions to custom UI components. Those methods are provided by ImageLoadingView protocol. This protocol is actually a trait - most of its methods are already implemented. All you need to do is to implement one required method to make your custom views conform to ImageLoadingView protocol.

You can do so by either implementing ImageDisplayingView protocol:

extension MKAnnotationView: ImageDisplayingView, ImageLoadingView {
    // That's it, you get default implementation of all methods in ImageLoadingView protocol
    public var nk_image: UIImage? {
        get { return self.image }
        set { self.image = newValue }
    }
}

Or providing an implementation for remaining ImageLoadingView methods:

extension MKAnnotationView: ImageLoadingView {
    public func nk_imageTask(task: ImageTask, didFinishWithResponse response: ImageResponse, options: ImageViewLoadingOptions) {
        // Handle task completion
    }
}

UICollection(Table)View

Sometimes you need to display a collection of images, which is a quite complex task when it comes to managing which tasks are associated with which cells, cancelling and handling those tasks, etc. Nuke takes care of all the complexity for you:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellReuseID, forIndexPath: indexPath)
    let imageView: ImageView = <#view#>
    imageView.image = nil
    imageView.nk_setImageWith(imageURL)
    return cell
}

Optional: you can also cancel image tasks as soon as the cells go offscreen:

func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
    let imageView: ImageView = <#view#>
    imageView.nk_cancelLoading()
}

Applying Filters

Nuke defines a simple ImageProcessing protocol that represents image filters. Some filters are already built into the framework. And it task just a couple line of code to create your own filters. You can also compose multiple filters together using ImageProcessorComposition class.

let filter1: ImageProcessing = <#filter#>
let filter2: ImageProcessing = <#filter#>

var request = ImageRequest(URL: <#image_url#>)
request.processor = ImageProcessorComposition(processors: [filter1, filter2])

Nuke.taskWith(request) {
    // Filters are applied, processed image is stored into memory cache
    let image = $0.image
}.resume()

Creating Filters

ImageProcessing protocol consists of two methods: one to process the image and one to compare two (heterogeneous) filters. Here's an example of custom image filter implemented on top of Core Image. It uses some of the helper functions provided by Nuke that simplify work with Core Image.

public class ImageFilterGaussianBlur: ImageProcessing {
    public let radius: Int
    public init(radius: Int = 8) {
        self.radius = radius
    }

    public func process(image: UIImage) -> UIImage? {
        return image.nk_filter(CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius" : self.radius]))
    }
}

// We need to be able to compare filters for equivalence to cache processed images
// Default implementation returns `true` if both filters are of the same class
public func ==(lhs: ImageFilterGaussianBlur, rhs: ImageFilterGaussianBlur) -> Bool {
    return lhs.radius == rhs.radius
}

Preheating Images

Preheating is an effective way to improve user experience in applications that display collections of images. Preheating means loading and caching images that might soon appear on the display. Nuke provides a set of self-explanatory methods for image preheating which are inspired by PHImageManager:

let requests = [ImageRequest(URL: imageURL1), ImageRequest(URL: imageURL2)]
Nuke.startPreheatingImages(requests: requests)
Nuke.stopPreheatingImages(requests: requests)

Automating Preheating

Nuke automates a process of determining which images in a UICollectionView (or UITableView) to preheat and when to start and stop preheating them. There are two corresponding classes (one for UICollectionView, one for UITableView). For more info about them see Image Preheating Guide.

let preheater = ImagePreheatingControllerForCollectionView(collectionView: <#collectionView#>)
preheater.delegate = self // Signals when preheat index paths change

Caching Images

Nuke provides both on-disk and in-memory caching.

For on-disk caching it relies on NSURLCache. The NSURLCache is used to cache original image data downloaded from the server. This class a part of the URL Loading System's cache management, which relies on HTTP cache.

For on-memory caching Nuke provides ImageMemoryCaching protocol and its implementation in ImageMemoryCache class built on top of NSCache. The ImageMemoryCache is used for fast access to processed images that are ready for display.

The combination of two cache layers results in a high performance caching system. For more info see Image Caching Guide which provides a comprehensive look at HTTP cache, URL Loading System and NSCache.

Accessing Memory Cache

Nuke automatically leverages both its cache layers. It accesses in-memory cache each time you start an ImageTask and calls a completion closure synchronously if the appropriate image is found.

If you need to access memory cache directly you might use the appropriate ImageManager methods:

let manager = ImageManager.shared
let request = ImageRequest(URL: NSURL(string: "")!)
let response = ImageCachedResponse(image: UIImage(), userInfo: nil)
manager.storeResponse(response, forRequest: request)
let cachedResponse = manager.cachedResponseForRequest(request)

Nuke.taskWith(_:) family of functions are just shortcuts for methods of the ImageManager class.

Customizing Image Manager

One of the great things about Nuke is that it is a pipeline that loads images using injectable dependencies. There are there protocols that you can use to customize that pipeline:

Protocol Description
ImageDataLoading Performs loading of image data (NSData)
ImageDecoding Decodes NSData to UIImage objects
ImageMemoryCaching Stores processed images into memory cache

You can either provide your own implementation of these protocols or customize existing classes that implement them. After you have all the dependencies in place you can create an `ImageManager`:
let dataLoader: ImageDataLoading = <#dataLoader#>
let decoder: ImageDecoding = <#decoder#>
let cache: ImageMemoryCaching = <#cache#>

let configuration = ImageManagerConfiguration(dataLoader: dataLoader, decoder: decoder, cache: cache)
ImageManager.shared = ImageManager(configuration: configuration)

Even if those protocols are not enough, you can take a look at the ImageLoading protocol. It provides a high level API for loading images for concrete ImageTasks. This protocol is implemented by the ImageLoader class that defines a common flow of loading images (load data -> decode -> process) and uses the corresponding ImageDataLoading, ImageDecoding and ImageProcessing protocols.

let loader: ImageLoading = <#loader#>
let cache: ImageMemoryCaching = <#cache#>

// The ImageManagerConfiguration(dataLoader:decoder:cache:) constructor is actually
// just a convenience initializer that creates an instance of ImageLoader class
let configuration = ImageManagerConfiguration(loader: loader, cache: cache)
ImageManager.shared = ImageManager(configuration: configuration)

Design

Protocol Description
ImageManager A top-level API for managing images
ImageDataLoading Performs loading of image data (NSData)
ImageDecoding Converts NSData to UIImage objects
ImageProcessing Processes decoded images
ImageMemoryCaching Stores processed images into memory cache

Installation

To install Nuke add a dependency to your Podfile:

# source 'https://github.com/CocoaPods/Specs.git'
# use_frameworks!
# platform :ios, "8.0" / :watchos, "2.0" / :osx, "10.9" / :tvos, "9.0"

pod "Nuke"
pod "Nuke-Alamofire-Plugin" # optional
pod "Nuke-AnimatedImage-Plugin" # optional

To install Nuke add a dependency to your Cartfile:

github "kean/Nuke"
github "kean/Nuke-Alamofire-Plugin" # optional
github "kean/Nuke-AnimatedImage-Plugin" # optional

Import

Import installed modules in your source files

import Nuke
import NukeAlamofirePlugin
import NukeAnimatedImagePlugin

Satellite Projects

Donations

This project has taken hundreds hours of work. If you find it useful, you can chip in for coffee to keep me going.

Alternatively, donate in ₽

Contacts

License

Nuke is available under the MIT license. See the LICENSE file for more info.