Downloading and caching images efficiently has long been a challenge for iOS developers.
Objective-C has lots of popular libraries to aid in this task, such as SDWebImage
, AFNetworking
, DFImageManager
and more. It’s taken some time for comparable Swift libraries to show up, but one of the newest is Nuke!
Nuke is an open source Swift framework for loading, processing, caching, displaying, and preheating images. It is written by Alexander Grebenyuk, an experienced developer who also wrote DFImageManager
.
In this article, I’ll give you a quick overview of installing Nuke and how to use it, and then outline the situations where you might find it useful in your apps. Let’s get started!
Getting Started
Installing Nuke is easy – it supports both CocoaPods and Carthage.
CocoaPods
If you use CocoaPods, simply add the following lines to your
pod "Nuke" pod "Nuke-Alamofire-Plugin" # optional pod "Nuke-AnimatedImage-Plugin" # optional |
Carthage
If you use Carthage, simply add the following lines to your Cartfile:
github "kean/Nuke" github "kean/Nuke-Alamofire-Plugin" # optional |
Currently, there is no Carthage support for the Nuke-AnimatedImage-Plugin.
What Nuke Does
Nuke helps you do 6 main things:
- Download Images
- Resize Images
- Cache Images
- Process Images
- Deserialize and Decode Images
- Preheat Images
Let’s look at each in turn.
1) Download Images
The primary goal of Nuke is to make downloading and using images in your app quick, easy, and efficient.
For example, the following snippet fetches an image for you:
Nuke.taskWithURL(url) { response in switch response { case let .Success(image, responseInfo): //do something with your image case let .Failure(error): //handle the error case } }.resume() |
Here, you simply pass in an instance of NSURL
then call resume
. Nuke will download your image in the background and call your response closure when it’s done.
response
is an enum
that describes the task’s outcome either as a success or a failure. responseInfo
provides useful information to the caller, such as fastResponse
, which tells you if the fetched image is from the network or the cache.
NSURLSessionTask
. In Nuke’s case, you’re using ImageTask
instead of NSURLSessionTask
.
Although ImageTask
doesn’t descend from NSURLSessionTask
, ImageTask
offers similar API methods such as resume
, suspend
, and cancel
for managing your network calls.
2) Resize Images
So far, this is no different than what you can do with NSURLSession
or other networking libraries. But you can also pass some image-specific parameters to your download task, by passing an ImageRequest
instead of an NSURL
, as in the example below:
var imageRequest = ImageRequest(URL: imageURL) request.targetSize = CGSize(width: 250, height: 250) request.contentMode = .AspectFit Nuke.taskWithRequest(request) { response in // Handle the response switch response { case let .Success(image, responseInfo): let image = image case let .Failure(error): let error = error } }.resume() |
This lets you specify elements such as the target size to resize the image to, and how to do so via contentMode
(AspectFill
or AspectFit
). Nuke will download and resize the image for you in the background.
Couldn’t you just hand UIImageView
a large image and let the resizing happen behind the scenes? You could do this, and UIImageView
would internally resize the image depending on the the image view’s contentMode
property. However, it’s better to resize the image yourself before handing it off to UIImageView
so you can keep memory usage at a minimum. That’s where Nuke comes in handy.
3) Cache Images
If you use Nuke, the images you download will be cached automatically. To understand how this works, check out the following quote from Nuke’s caching mechanism guide:
Rather than reinventing caching, Nuke relies on two built-in caching mechanisms offered by Apple:
NSURLSession
‘s caching mechanism viaNSURLCache
NSCache
NSURLSession
provides both disk and in-memory caching; it’s controlled primarily by the HTTP headers sent back in response to your request. NSCache
is an in-memory cache that sits on top of the URL loading framework’s caching mechanism to make things even faster.
The added benefit of using NSURLCache
is that it’s completely transparent to the user. After requesting an image, you can verify its existence in the cache by checking the default NSURLCache
singleton like so:
let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(imageRequest) |
4) Process Images
Besides downloading and caching the image, ImageRequest
also allows you define custom filters to apply to the fetched image. Yes, you could technically do this by yourself, but it’s still a nice touch.
The code below shows how easy it can be to add a blur effect to your retrieved image:
let blurringImageFilter : ImageProcessing = MyFilters.blurringFilter() var imageRequest = ImageRequest(URL: imageURL) imageRequest.processor = blurringImageFilter Nuke.taskWithRequest(request) { response in switch response { // Handle the response case let .Success(image, responseInfo): let image = image case let .Failure(error): let error = error } }.resume() |
The key element in the code above is Nuke’s ImageProcessing
protocol. It defines several methods you must comply with; the most important one is this:
func processImage(image: UIImage) -> UIImage? |
You pass this method an instance of UIImage
and it returns an UIImage
optional. If the filter operation succeeds, you get a new image with the filter already applied. If Nuke can’t apply the filter for whatever reason, it returns nil
. It’s quite straightforward!
5) Deserialize & Decode Images
Once you request an image from the network, there are two things that must happen before iOS can display it on the screen: image deserialization, and image decoding.
Image Deserialization
The first is image deserialization, which translates a binary blob to the image file.
UIImage
can handle deserialization for you, but this potentially expensive operation usually happens on the main thread and can affect your UI. Nuke helps you maintain a buttery smooth UI by handling deserialization in the background. This occurs in Nuke’s ImageDecoder
class, with the important bit as follows:
return UIImage(data: data, scale: UIScreen.mainScreen().scale) |
Note: There isn’t anything in Apple’s official documentation stating init?(data data: NSData, scale scale: CGFloat)
is thread-safe. Nevertheless, from an empirical point of view, and mostly agreed upon in the development community at large, init?(data data: NSData, scale scale: CGFloat)
works outside the main thread.
Image Decoding
The step after deserialization is image decoding. An image always comes to you encoded in a particular image format, usually denoted by the file’s extension, such as .JPG or .PNG. Image decoding takes the encoded file and uses image standards to translate the file into a 2-D grid of colors to show on the screen.
The good news is that UIImage
and UIImageView
handle image decoding for you automatically. The bad news is that, like deserialization, decoding usually happens on the main thread, which again can degrade UI performance.
Nuke handles image decoding off the main thread as well. Nuke’s ImageDecompressor
class internally forces early decompression into a Core Graphics context in the background. Very cool! :]
6) Preheat Images
The most exciting feature in Nuke is probably the ability to preheat images.
If you’re an expert at preheating food but don’t know how to preheat images, here’s what it means: you make the image request at some time well before you need to display the image, download it and store it in the application’s network cache.
Later when you need to display the image, the response to your request will come back from the cache instead of the network, which is much faster and more reliable than making a network request and hoping it will succeed and download in enough time before your user notices the delay.
Imagine you have have a screen that displays an article with an image at the bottom; you know there’s a good chance the user will eventually scroll down to see the image. With Nuke, you can preheat the image as soon as the user opens the article; as soon as she scrolls down the image will load so quickly as to give the impression it was there all along.
Here’s how you preheat an image with Nuke:
let articleImagesRequests = article.imagesRequests() // get the article's images requests Nuke.startPreheatingImages(articleImagesRequests) // start the oven |
Stopping the operation is just as easy:
Nuke.stopPreheatingImages(articleImagesRequests) |
The great thing about this approach is that explicit image requests are of a higher priority than preheating requests. Grebenyuk wrote a comprehensive explanation of this feature in the Nuke repo’s wiki.
Preheating and UICollectionView
Nuke also extends its preheating functionality to work seamlessly with UICollectionViews
by way of ImagePreheatingControllerForCollectionView
and the ImagePreheatingControllerDelegate
protocol, which defines a single method:
func preheatingController(controller: ImagePreheatingController, didUpdateWithAddedIndexPaths addedIndexPaths: [NSIndexPath], removedIndexPaths: [NSIndexPath]) |
This delegate method passes in two important index path arrays:
addedIndexPaths
: The index paths to preheat.removedIndexPaths
: The index paths to remove from preheating.
How does ImagePreheatingControllerForCollectionView
know which index paths to start and stop preheating? Basically, it observes the UICollectionView
‘s UIScrollView
contentOffSet
and figures out which cells are coming in and out of the view port, taking into consideration the scrolling direction and the device orientation.
If you want to use this feature with UITableView
, you can use ImagePreheatingControllerForCollectionView
‘s superclass ImagePreheatingController
. This class observes UIScrollView
directly, which UITableView
subclasses. However, a little bird told me that full support for UITableView
is coming soon! :]
To see a small demo of preheating in action, check out the example in Nuke’s repository here and the PreheatingDemoViewController.swift
class.
When Should You Use Nuke?
You should always ask the following two questions before you adopt a third party library:
- Is it being maintained?
- Does it do what it’s supposed to do?
You can answer the first question by checking the frequency of commits and if open issues are being dealt with in a timely fashion. In this case, I think Grebenyuk is doing an excellent job on both fronts! You can check the commit history and the issues list of the repo to see for yourself.
The second question isn’t trivial to answer; determining whether it’s true or false depends on your project. The easiest way is to start using the library see if it does exactly what it says on the tin. You can do this using Instruments and see how it behaves in different scenarios. In this case, Allocations, Time Profiler and Leaks would be your best bets. Once you’ve checked that it behaves as expected in your particular use case, you’re good to go.
In my day to day experience with Nuke, it’s worked as promised; as a standard image fetcher and cacher, Nuke does a flawless job.
Alternatives to Nuke
There are other interesting libraries out there that are comparable to Nuke; one of them is Kingfisher. Although Kingfisher is a perfectly valid option, the preheating feature offered by Nuke was something I really needed, so having it work out of the box made it an easy decision to use Nuke.
On the other hand, if you’re already using something like Alamofire, you could probably just use its companion library AlamofireImage for image handling. It’s difficult to justify the use of Nuke in this case, just for the sake of preheated images.
Alamofire.Manager
comply with Nuke’s ImageDataLoading
protocol; this means you can use either Nuke’s default image loader, or the Alamofire loader if you choose.
Why is this useful? If you’re already using Alamofire in your project and decide to leverage Nuke as well, this plugin passes the network tasks of image loading to Alamofire, while preserving Nuke’s caching, processing and decoding abilities. It’s the best of both worlds!
If you need more powerful image processing than Nuke can provide, you could make a combo with Alamofire and Path’s popular FastImageCache to solve your particular problem.
Where to Go From Here?
All in all, Nuke is a great library. Its advanced features and constant updates make it an easy choice for many projects. For more information check out the official Nuke Github repository.
If you have any questions or comments on Nuke, feel free to join the discussion below!
The post WTF is… Nuke? appeared first on Ray Wenderlich.