GitHub - kean/Nuke: A powerful image loading and caching framework
source link: https://github.com/kean/Nuke
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.md
A powerful image loading and caching system which takes care of image loading and displaying for you. It makes simple tasks like loading images into image view extremely simple, while also supporting and progressively disclosing more advanced features for more demanding apps.
Features
- Two cache layers, fast LRU memory cache
- Alamofire, FLAnimatedImage, Gifu integrations
- RxNuke with RxSwift extensions
- Automates prefetching with Preheat (deprecated in iOS 10)
- Small (under 1500 lines), fast and reliable
- Progressive image loading (Progressive JPEG)
- Resumable downloads, request deduplication, rate limiting and more
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:
- Complete Usage Guide - best place to start
- Detailed Image Pipeline description
- Section dedicated to Performance
- List of available Extensions
- List of Requirements
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
Enabling Progressive Decoding (Beta)
You need a pipeline with progressive decoding enabled:
let pipeline = ImagePipeline { $0.isProgressiveDecodingEnabled = true }
And that's it, you can start observing images as they are produced by the pipeline:
let imageView = UIImageView() let task = pipeline.loadImage(with: url) { imageView.image = $0.value } task.progressiveImageHandler = { imageView.image = $0 }
The progressive decoding only kicks in when Nuke determines that the image data does contain a progressive JPEG. The decoder intelligently scans the data and only produces a new image when it receives a full new scan (progressive JPEGs normally have around 10 scans).
See "Progressive Decoding" demo to see progressive JPEG in practice. You can also uncomment the code that blurs the first few scans of the image which makes them look a bit nicer.
Using RxNuke
RxNuke adds RxSwift extensions for Nuke and enables many common use cases:
- Going from low to high resolution
- Loading the first available image
- Showing stale image while validating it
- Load multiple images, display all at once
- Auto retry on failures
- And more...
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 DescriptionDataLoading
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:
ImagePipeline
checks if the image is in memory cache (ImageCaching
). Returns immediately if finds it.ImagePipeline
uses underlying data loader (DataLoading
) to fetch (or return cached) image data.- When the image data is loaded it gets decoded (
DataDecoding
) creating an image object. - The image is then processed (
ImageProcessing
). 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. Supports both validators (ETag
, Last-Modified
). 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) po task.metrics Task Information { Task ID - 5 Total Duration - 0.363 Was Cancelled - false Is Memory Cache Hit - false Was Subscribed To Existing Image Loading Session - false } Timeline { 12:42:06.559 - Start Date 12:42:06.923 - End Date } Image Loading Session { Session Information { Session ID - 5 Total Duration - 0.357 Was Cancelled - false } Timeline { 12:42:06.566 - Start Date 12:42:06.570 - Data Loading Start Date 12:42:06.904 - Data Loading End Date 12:42:06.909 - Decoding Start Date 12:42:06.912 - Decoding End Date 12:42:06.913 - Processing Start Date 12:42:06.922 - Processing End Date 12:42:06.923 - End Date } Resumable Data { Was Resumed - nil Resumable Data Count - nil Server Confirmed Resume - nil } }
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 GIFsRequirements
- 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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK