Swiftpack.co - Package - kean/Nuke

A powerful image loading and caching framework which allows for hassle-free image loading in your app. Simple tasks like loading images into image view are made simple, while more advanced features are disclosed progressively.

Nuke takes performance seriously. It employs techniques across all key performance areas including asynchronous design, resumable downloads, request deduplication, prefetching, rate limiting, CoW structs, LRU cache, and many more.

Features

Nuke 7 is in active development, first beta is already available. It's an early version, the documentation hasn't been fully updated yet and there are still some upcoming changes. If you'd like to contribute or have some suggestions or feature requests please open an issue, a pull request or contact me on Twitter.

Quick Start

Upgrading from the previous version? Use a Migration Guide.

This README has five sections:

More information is available in Documentation directory and a full API Reference. When you are ready to install Nuke you can follow an Installation Guide - all major package managers are supported.

Usage

Loading Images into Targets

You can load an image into an image view with a single line of code:

Nuke.loadImage(with: url, into: imageView)

Nuke will automatically load image data, decompress it in the background, store image in memory cache and display it.

To learn more about the image pipeline see the next section.

Nuke keeps track of each target. When you request an image for a target any previous outstanding requests get cancelled. The same happens automatically when the target is deallocated.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    ...
    // Prepare image view for reuse.
    cell.imageView.image = nil

    // Previous requests for the image view get cancelled.
    Nuke.loadImage(with: url, into: cell.imageView)
    ...
}

Targets

What can be a target? Anything that implements ImageTarget protocol:

public protocol ImageTarget: class {
    /// Callback that gets called when the request is completed.
    func handle(response: Result<Image>, isFromMemoryCache: Bool)
}

Nuke extends UIImageView (NSImageView on macOS) to adopt ImageTarget protocol. You can do the same for you own classes.

Customizing Requests

Each request is represented by a ImageRequest struct. A request can be created with either URL or URLRequest.

var request = ImageRequest(url: url)
// var request = ImageRequest(urlRequest: URLRequest(url: url))

// Change memory cache policy:
request.memoryCacheOptions.writeAllowed = false

// Update the request priority:
request.priority = .high

Nuke.loadImage(with: request, into: imageView)

Processing Images

Nuke can process images for you. The first option is to resize the image using a Request:

/// Target size is in pixels.
ImageRequest(url: url, targetSize: CGSize(width: 640, height: 320), contentMode: .aspectFill)

To perform a custom tranformation use a processed(key:closure:) method. Her's how to create a circular avatar using Toucan:

ImageRequest(url: url).process(key: "circularAvatar") {
    Toucan(image: $0).maskWithEllipse().image
}

All of those APIs are built on top of ImageProcessing protocol. If you'd like to you can implement your own processors that adopt it. Keep in mind that ImageProcessing also requires Equatable conformance which helps Nuke identify images in memory cache.

See Core Image Integration Guide for more info about using Core Image with Nuke

Using Image Pipeline

You can use ImagePipeline to load images directly without a target. ImagePipeline offers a convenience closure-based API for loading images:

let task = ImagePipeline.shared.loadImage(with: url) { result in
    // Handle response
}

task.progress = {
    print("progress updated")
}

// task.cancel()
// task.setPriority(.high)

Tasks can be used to track download progress, cancel the requests, and dynamically udpdate download priority.

Configuring Image Pipeline

ImagePipeline is initialized with a Configuration which makes it fully customizable:

let pipeline = ImagePipeline {
    $0.dataLoader = /* your data loader */
    $0.dataLoadingQueue = OperationQueue() /* your custom download queue */
    $0.imageCache = /* your image cache */
    /* etc... */
}

// When you're done you can make the pipeline a shared one:
ImagePipeline.shared = pipeline

Using Memory and Disk Cache

Default Nuke's ImagePipeline has two cache layers.

First, there is a memory cache for storing processed images ready for display. You can get a direct access to this cache:

// Configure cache
ImageCache.shared.costLimit = 1024 * 1024 * 100 // 100 MB
ImageCache.shared.countLimit = 100

// Read and write images
let request = ImageRequest(url: url)
ImageCache.shared[request] = image
let image = ImageCache.shared[request]

// Clear cache
ImageCache.shared.removeAll()

To store unprocessed image data Nuke uses a URLCache instance:

// Configure cache
DataLoader.sharedUrlCache.diskCapacity = 100
DataLoader.sharedUrlCache.memoryCapacity = 0

// Read and write responses
let request = ImageRequest(url: url)
let _ = DataLoader.sharedUrlCache.cachedResponse(for: request.urlRequest)
DataLoader.sharedUrlCache.removeCachedResponse(for: request.urlRequest)

// Clear cache
DataLoader.sharedUrlCache.removeAllCachedResponses()

Preheating Images

Preheating (prefetching) means loading images ahead of time in anticipation of their use. Nuke provides a ImagePreheater class that does just that:

let preheater = ImagePreheater(pipeline: ImagePipeline.shared)

let requests = urls.map {
    var request = Request(url: $0)
    request.priority = .low
    return request
}

// User enters the screen:
preheater.startPreheating(for: requests)

// User leaves the screen:
preheater.stopPreheating(for: requests)

You can use Nuke in combination with Preheat library which automates preheating of content in UICollectionView and UITableView. On iOS 10.0 you might want to use new prefetching APIs provided by iOS instead.

Check out Performance Guide to see what else you can do to improve performance

Using RxNuke

RxNuke adds RxSwift extensions for Nuke and enables many common use cases:

Here's an example of how easy it is to load go flow log to high resolution:

let pipeline = ImagePipeline.shared
Observable.concat(pipeline.loadImage(with: lowResUrl).orEmpty,
                  pipeline.loadImage(with: highResUtl).orEmpty)
    .subscribe(onNext: { imageView.image = $0 })
    .disposed(by: disposeBag)

Image Pipeline

Nuke's image pipeline consists of roughly five stages which can be customized using the following protocols:

|Protocol|Description| |--------|-----------| |DataLoading|Download (or return cached) image data| |DataDecoding|Convert data into image objects| |ImageProcessing|Apply image transformations| |ImageCaching|Store image into memory cache|

All those types come together the way you expect:

  1. ImagePipeline checks if the image is in memory cache (ImageCaching). Returns immediately if finds it.
  2. ImagePipeline uses underlying data loader (DataLoading) to fetch (or return cached) image data.
  3. When the image data is loaded it gets decoded (DataDecoding) creating an image object.
  4. The image is then processed (ImageProcessing).
  5. ImagePipeline stores the processed image in the memory cache (ImageCaching).

Nuke is fully asynchronous (non-blocking). Each stage is executed on a separate queue tailored specifically for it. Let's dive into each of those stages.

Data Loading and Caching

A built-in DataLoader class implements DataLoading protocol and uses Foundation.URLSession to load image data. The data is cached on disk using a Foundation.URLCache instance, which by default is initialized with a memory capacity of 0 MB (Nuke stores images in memory, not image data) and a disk capacity of 150 MB.

See Image Caching Guide to learn more about image caching

See Third Party Libraries guide to learn how to use a custom data loader or cache

Most developers either implement their own networking layer or use a third-party framework. Nuke supports both of those workflows. You can integrate your custom networking layer by implementing DataLoading protocol.

See Alamofire Plugin that implements DataLoading protocol using Alamofire framework

Memory Cache

Processed images which are ready to be displayed are stored in a fast in-memory cache (ImageCache). It uses LRU (least recently used) replacement algorithm and has a limit which prevents it from using more than ~20% of available RAM. As a good citizen, ImageCache automatically evicts images on memory warnings and removes most of the images when the application enters background.

Resumable Downloads (Beta)

If the data task is terminated (either because of a failure or a cancellation) and the image was partially loaded, the next load will resume where it was left off. The resumable downloads are enabled by default.

By default resumable data is stored in an efficient memory cache. Future versions might include more customization.

Request Dedupication

By default ImagePipeline combines the requests with the same loadKey into a single task. The task's priority is set to the highest priority of registered requests and gets updated when requests are added or removed to the task. The task only gets cancelled when all the registered requests are.

Deduplication can be disabled using ImagePipeline.Configuration.

Performance

Performance is one of the key differentiating factors for Nuke. There are four key components of its performance:

Main-Thread Performance

The framework has been tuned to do very little work on the main thread. In fact, it's at least 2.3x faster than its fastest competitor. There are a number of optimizations techniques that were used to achieve that including: reducing number of allocations, reducing dynamic dispatch, backing some structs by reference typed storage to reduce ARC overhead, etc.

Robustness Under Stress

A common use case is to dynamically start and cancel requests for a collection view full of images when scrolling at a high speed. There are a number of components that ensure robustness in those kinds of scenarios:

  • ImagePipeline schedules each of its stages on a dedicated queue. Each queue limits the number of concurrent tasks. This way we don't use too much system resources at any given moment and each stage doesn't block the other. For example, if the image doesn't require processing, it doesn't go through the processing queue.
  • Under stress ImagePipeline will rate limit the requests to prevent trashing of the underlying systems (e.g. URLSession).

Memory Usage

  • Nuke tries to free memory as early as possible.
  • Memory cache uses LRU (least recently used) replacement algorithm. It has a limit which prevents it from using more than ~20% of available RAM. As a good citizen, ImageCache automatically evicts images on memory warnings and removes most of the images when the application enters background.

Performance Metrics (Beta)

Nuke captures detailed metrics on each image task:

(lldb) p task.metrics
(Nuke.ImageTask.Metrics) $R2 = {
  taskId = 9
  timeCreated = 545513853.67615998
  timeResumed = 545513853.67778301
  timeCompleted = 545513860.90999401
  session = 0x00007b1c00011100 {
    sessionId = 9
    timeDataLoadingStarted = 545513853.67789805
    timeDataLoadingFinished = 545513853.74310505
    timeDecodingFinished = 545513860.90150297
    timeProcessingFinished = 545513860.90990996
    urlResponse = 0x00007b0800066960 {
      ObjectiveC.NSObject = {}
    }
    downloadedDataCount = 35049
  }
  wasSubscibedToExistingTask = false
  isMemoryCacheHit = false
  wasCancelled = false
}

Extensions

|Name|Description| |--|--| |RxNuke|RxSwift extensions for Nuke with examples of common use cases solved by Rx| |Alamofire|Replace networking layer with Alamofire and combine the power of both frameworks| |Gifu|Use Gifu to load and display animated GIFs| |FLAnimatedImage|Use FLAnimatedImage to load and display animated GIFs|

Requirements

  • iOS 9.0 / watchOS 2.0 / macOS 10.10 / tvOS 9.0
  • Xcode 9.3
  • Swift 4.1

License

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

Github

link
Stars: 2694
Help us keep the lights on

Dependencies

Used By

Total: 0

Releases

7.0-beta1 - Apr 16, 2018

Nuke 7 is the next major milestone that continues the trend started in Nuke 6 making the project more pragmatic and mature. Nuke 7 is more ergonomic, fast and powerful.

Nuke 7.0-beta1 is an early release (compared to 6.0-beta1). There are still some major changes coming in the next betas. To make migration easier Nuke 7 is almost fully source compatible with Nuke 6, but many APIs were deprecated and will be removed soon.

There are four major new features in Nuke 7:

Resumable Downloads (Beta)

If the data task is terminated (either because of a failure or a cancellation) and the image was partially loaded, the next load will resume where it was left off. The resumable downloads are enabled by default.

By default resumable data is stored in an efficient memory cache. Future versions might include more customization.

In many use cases reusable downloads are a massive improvement. Next betas will feature more customization options for resumable downloads (e.g. customizable resumable data storage).

Image Pipelines (Beta)

The previous Manager + Loading architecture (terrible naming, responsibilities are often confused) was replaced with a new unified ImagePipeline class. There is also a new ImageTask class which feels the gap where user or pipeline needed to communicate between each other after the request was started.

ImagePipeline and ImageTask have a bunch of new features:

  • In ImagePipeline.Configuration you can now provider custom queues (OperationQueue) for data loading, decoding and processing (each stage). This way you have more access to queuing (e.g. you can change qualityOfService, suspend queues) etc and you can also use the same queue across different pipelines.
  • There are two APIs: convenience ones with blocks (loadImage(with:completion:)) and new one imageTask(with:) which returns new ImageTask class which gives you access to more advanced features. To start a task call resume() method, to cancel the task call cancel().
  • Dynamically change priority of executing tasks.
  • Set a custom shared ImagePipeline.

Progressive Image Decoding (WIP)

This feature is still in development and might be coming in one of the next beta.

Performance Metrics (Beta)

Nuke captures detailed metrics on each image task:

(lldb) p task.metrics
(Nuke.ImageTask.Metrics) $R2 = {
  taskId = 9
  timeCreated = 545513853.67615998
  timeResumed = 545513853.67778301
  timeCompleted = 545513860.90999401
  session = 0x00007b1c00011100 {
    sessionId = 9
    timeDataLoadingStarted = 545513853.67789805
    timeDataLoadingFinished = 545513853.74310505
    timeDecodingFinished = 545513860.90150297
    timeProcessingFinished = 545513860.90990996
    urlResponse = 0x00007b0800066960 {
      ObjectiveC.NSObject = {}
    }
    downloadedDataCount = 35049
  }
  wasSubscibedToExistingTask = false
  isMemoryCacheHit = false
  wasCancelled = false
}

Other Changes

Improvements

  • Improve main-thread performance by another 20%.
  • ImagePreheater now checks ImageCache synchronously before creating tasks which makes it more efficient.

Removed

  • Users were confused by separate set of loadImage(with:into:handler:) methods so they were removed. There were adding very little convenience for a lot of mental overhead. It's fairly easy to reimplement them the way you want

Reworked

  • Prefix all classes with Image starting with a new ImagePipeline. This makes code more readable. It felt awkward to use types like ‘Request’ in your project. ‘Request’ is an integral part of ‘Nuke’, but you are only using it in your project once or twice.

6.1.1 - Apr 9, 2018

  • Lower macOS deployment target to 10.10 - #156
  • Improve README: add detailed Image Pipeline section, Performance section, rewrite Usage guide

6.1 - Feb 24, 2018

Features

  • Add Request.Priority with 5 available options ranging from .veryLow to .veryHigh. One of the use cases of Request.Priority is to lower the priority of preheating requests. In case requests get deduplicated (multiple requests are handled by a single task) the task's priority is set to the highest priority of registered requests and gets updated when requests are added or removed from the task.

Improvements

  • Fix warnings on Xcode 9.3 beta 3
  • Loader implementation changed a bit, it is less clever now and is able to accommodate new features like request priorities
  • Minor changes in style guide to make codebase more readable
  • Switch to native NSLock, there doesn't seem to be any performance wins anymore when using pthread_mutex directly

Fixes

  • #146 fix disk cache path for macOS, thanks to @willdahlberg

6.0 - Dec 23, 2017

About 8 months ago I finally started using Nuke in production. The project has matured from a playground for experimenting with Swift features to something that I rely on in my day's job.

There are three main areas of improvements in Nuke 6:

  • Performance. The primary loadImage(with:into:) method is now 1.5x faster thanks to cumulative performance improvements to CancellationToken, Manager, Request and Cache. And it's not just main thread performance, many of the background operations were also optimized.
  • API refinements. Some common operations that were surprisingly hard are easy now. And there the implementation details are no longer leaking into a public API (e.g. classes like Deduplicator).
  • Fixed inconveniences like Thread Sanitizer warnings (false positives!). Improved compile time. Better documentation.

New APIs

  • Implements progress reporting https://github.com/kean/Nuke/issues/81
  • Scaling images is now easier and more discoverable with new convenience Request initialisers (Request.init(url:targetSize:contentMode: and Request.init(urlRequest:targetSize:contentMode:)
  • Add a way to add anonymous image processors to the request (Request.process(key:closure:) and Request.processed(key:closure:))
  • Add Loader.Options which can be used to configure Loader (e.g. change maximum number of concurrent requests, disable deduplication or rate limiter, etc).

Improvements

  • Improve CancellationTokenSource, Loader, TaskQueue, Manager, Request, Cache performance
  • Parallelize image processing for up to 2x performance boost in certain scenarios. Might increase memory usage. The default maximum number of concurrent tasks is 2 and can be configured using Loader.Options.
  • Loader now always calls completion on the main thread
  • Move URLResponse validation from DataDecoder to DataLoader
  • Make use of some Swift 4 feature like nested types inside generic types
  • Improve compile time
  • Wrap Loader processing and decoding tasks into autoreleasepool which reduced memory footprint

Fixes

  • Get rid of Thread Sanitizer warnings in CancellationTokenSource (false positive)
  • Replace Foundation.OperationQueue & custom Foundation.Operation subclass with a new Queue type. It's simpler, faster, and gets rid of pesky Thread Sanitizer warnings https://github.com/kean/Nuke/issues/141

Removed APIs

  • Remove global loadImage(...) functions https://github.com/kean/Nuke/issues/142
  • Remove static Request.loadKey(for:) and Request.cacheKey(for:) functions. The keys are now simply returned in Request's loadKey and cacheKey properties which are also no longer optional now.
  • Remove Deduplicator class, make this functionality part of Loader. This has a number of benefits: reduced API surface, improves performance by reducing the number of queue switching, enables new features like progress reporting.
  • Remove Scheduler, AsyncScheduler, Loader.Schedulers, DispatchQueueScheduler, OperationQueueScheduler. This whole infrastructure was way too excessive.
  • Make RateLimiter private
  • DataLoader now works with URLRequest, not Request

6.0-beta2 - Dec 17, 2017

  • Fix memory leak in Loader - regression introduced in 6.0-beta1
  • Get rid of Thread Sanitizer warnings in CancellationTokenSource (false positive)
  • Improve performance of CancellationTokenSource
  • Improve Cache hits and writes performance by ~15%
  • Improve Loader performance