Nuke Tutorial for iOS: Getting Started
In this Nuke tutorial, you’ll learn how to integrate Nuke using Swift Package Manager and use it to load remote images, both with and without Combine. By Ehab Amer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Nuke Tutorial for iOS: Getting Started
30 mins
- Getting Started
- Setting up Nuke
- Installing with Swift Package Manager
- Using Nuke: Basic Mode
- Setting Loading Options
- Monitoring Memory Usage
- Using Instruments
- Advanced Nuking
- Loading with Requests
- Optimizing Code
- Using ImagePipeline to Load Images
- Caching Images
- Combining with Combine
- Setting up ImagePublisher
- Using ImagePublisher
- Chaining Requests
- Cleaning the Subscriber
- Where to Go From Here?
Caching Images
Nuke has a cool feature that allows you to aggressively cache images. What does this mean? Well, it means it will ignore the Cache-Control
directives that may be found in HTTP headers.
But why would you want to do this? Sometimes, you know that the data is not going to change. This is often the case for images found on the web. Not always, but often.
If your app is working with images that you know shouldn’t change, it’s a good idea to set up Nuke to use an aggressive image cache and not risk reloading the same images.
In PhotoGalleryViewController.swift, go back to viewDidLoad()
and add this code to the end:
// 1
DataLoader.sharedUrlCache.diskCapacity = 0
let pipeline = ImagePipeline {
// 2
let dataCache = try? DataCache(name: "com.raywenderlich.Far-Out-Photos.datacache")
// 3
dataCache?.sizeLimit = 200 * 1024 * 1024
// 4
$0.dataCache = dataCache
}
// 5
ImagePipeline.shared = pipeline
Here, you:
- Disable the default disk cache by setting its capacity to zero. You don’t want to accidentally cache the image twice. You’ll create your own cache.
- Create a new data cache.
- Set the cache size limit to 200 MB (since there are 1,024 bytes per kilobyte and 1,024 kilobytes per megabyte).
- Configure the
ImagePipeline
to use the newly createdDataCache
. - Set this image pipeline to be the default one used when none is specified.
That’s it!
If you build and run your code now, you won’t see a difference. However, behind the scenes your app is caching all images to disk and using the cache to load anything it can.
ImagePipeline
:
if let dataCache = ImagePipeline.shared
.configuration.dataCache as? DataCache {
dataCache.removeAll()
}
if let dataCache = ImagePipeline.shared
.configuration.dataCache as? DataCache {
dataCache.removeAll()
}
Combining with Combine
Nuke supports integration with other frameworks through its extensions. In the next part, you’ll learn how to combine using Nuke with the Combine framework. :]
Combine is a framework Apple released with iOS 13 to provide declarative APIs through publisher-subscriber structure. It greatly facilitates the handling of asynchronous events and allows you to combine event-processing operations.
Setting up ImagePublisher
You need to install a Nuke extension named ImagePublisher. Install it the same way you did earlier for Nuke using the Swift Package Manager.
Add a new package from the URL: https://github.com/kean/ImagePublisher.git.
Before you start using the new extension, note what’s currently happening in your app.
First, the gallery downloads all the images and stores a resized version of the images in its cache. Second, when you open any photo, the full image starts to download and, while that happens, you see a placeholder image.
So far that makes sense, but that is not the best experience you want to provide to your users. You are showing a generic placeholder image although you have a resized image already stored. Ideally, you want to show that photo instead until the full image is downloaded.
The best way to do this is to request the resized image, which most likely will be almost instantly available if it was already cached. Then, right after that request is finished, fetch the full-sized one. In other words: You want to chain the two requests.
But first, test your skills at basic requests with ImagePublisher
.
Using ImagePublisher
In PhotoViewController.swift, add the following imports at the top of the file:
import Combine
import ImagePublisher
Then add the following properties at the top of the class.
//1
var cancellable: AnyCancellable?
//2
var resizedImageProcessors: [ImageProcessing] = []
-
cancellable
, in very simple terms, is a handle for an operation that — as its name says — is cancellable. You’ll use it to refer to the request operation you’ll create to download the images. -
resizedImageProcessors
is like what you used inPhotoGalleryViewController
when you built the request for the resized image. You want thePhotoGalleryViewController
as it’s opening a newPhotoViewController
to provide the same processors so you can make an identical request. If the request is different, then a new photo will download, but you want the same one showing in the gallery so you can fetch it from the cache whenever possible.
At the end of the class, add the new method
func loadImage(url: URL) {
// 1
let resizedImageRequest = ImageRequest(
url: url,
processors: resizedImageProcessors)
// 2
let resizedImagePublisher = ImagePipeline.shared
.imagePublisher(with: resizedImageRequest)
// 3
cancellable = resizedImagePublisher
.sink(
// 4
receiveCompletion: { [weak self] response in
guard let self = self else { return }
switch response {
case .failure:
self.imageView.image = ImageLoadingOptions.shared.failureImage
self.imageView.contentMode = .scaleAspectFit
case .finished:
break
}
},
// 5
receiveValue: {
self.imageView.image = $0.image
self.imageView.contentMode = .scaleAspectFill
}
)
}
Here’s what this does:
- Create a new request with the provided image URL, using the processors you defined earlier.
- Create a new publisher for processing this request. A publisher is in effect a stream of values that either completes or fails. In the case of this publisher, it will process the request and then either return the image or fail.
- Execute this publisher, or in Combine lingo: Create a subscriber with
sink(receiveCompletion:receiveValue:)
on this publisher with two closures. - The first closure provides the result from the publisher, whether it managed to finish its operation normally or it failed. In case of failure, you show the failure image with an aspect fit content mode. And if it finishes normally, then you do nothing here because you’ll receive the value in the second closure.
- Show the value you received normally with an aspect fill. Know that you don’t receive any values if the first closure had a failure response.
In viewDidLoad()
, remove all of the following code that is responsible for loading the image:
ImagePipeline.shared.loadImage(
with: imageURL) { [weak self] response in // 4
guard let self = self else {
return
}
switch response {
case .failure:
self.imageView.image = ImageLoadingOptions.shared.failureImage
self.imageView.contentMode = .scaleAspectFit
case let .success(imageResponse):
self.imageView.image = imageResponse.image
self.imageView.contentMode = .scaleAspectFill
}
}
And replace it with the call to the newly added method:
loadImage(url: imageURL)
Now go to PhotoGalleryViewController.swift and find collectionView(_:didSelectItemAt:)
. Right before you push the view controller onto the navigation stack, add this line:
photoViewController.resizedImageProcessors = resizedImageProcessors
On your simulator, uninstall the app completely with a long press on the app icon, then choose Delete App. This completely deletes the cached images, among everything else related to the app, so you can start fresh.
Build and run. Tap any image that has finished loading and see that it appears instantly without showing the loading placeholder. But the image will not be as sharp as it used to be. This is because you are loading the same resized image from the gallery and Nuke is providing it to you with the second request from the cache.
You’re not done yet. You want to load the full-sized image, too.