[Swift] How to Asynchronously Download and Cache Images without Relying on Third-Party Libraries

Update October 2016: Fully updated for Xcode 8 and Swift 3

Loading a massive number of images asynchronously in a scrollable view like UITableView can be a common task. However, keeping the app responsive on scrolling while images are being downloaded can be a bit of challenge.

Many developers rely on libraries like Alamofire and SDWebImage to avoid the background image loading eventual troubles and cache management hassle. Something we did a while ago in this tutorial.

But what if you want to do all of that with your own pure code, without the help of third-party libraries ?

Well, that is what you gonna do here and now :]

In this tutorial, you are going to use your skills to build a smooth app that pull out a list of games titles and icons from the iTunes API. Along the way, you will understand how GCD (Grand Central Dispatch) and NSCache work together to manage networking images and produce a neat cache management system.

Without further ado, let’s build it!

Open up Xcode, select “File\New\Project” from the menu, choose the Single View Application template and name the project “ImagesDownloadAndCache”.

Create new Xcode project

Setting up the UI

The app you are about to build consists of a single interface, a table view with a refresh control to load the content.

The Xcode storyboard file comes with a default UIViewController, let’s remove it and replace with a UITableViewController instead, since it has a refresh control property by default.

Select Main.storyboard from the Project navigator view, drag a UITableViewController object from the Object library to the canvas. Next, select the initial View Controller and hit delete from the keyboard.

Note: Choose an iPhone model to work with along this tutorial, I selected the iPhone 6s model.

Choose a device model to layout with from the "View as" panel in Xcode 8

Select the recently added Table View Controller, then check the “Is Initial View Controller” from the Attributes inspector view.

Check Is Initial View Controller

Select ViewController.swift from the Project navigator view and change the class declaration so that it’s a UITableViewController subclass.

Switch back to the storyboard file, select the Table View Controller from the canvas then set the class to ViewController in the Identity inspector view.

Set the custom class for the View Controller in the Xcode 8 Identity inspector

Also, let’s add a navigation controller to the view hierarchy so that you can benefit from the navigation bar layout. To do so, make sure the View Controller is selected in the canvas then select “Editor\Embed In\Navigation Controller” from the menu.

As a last step, let’s assign an identifier for the custom cell so that you can reuse it from your code.

Select the Table View Cell from the canvas or from within the Document Outline view, then from the Attributes Inspector set the Identifier to “GameCell”.

Set an Identifier for a Custom Cell in Xcode 8

That’s it for the layout, now it’s time for some code!

Select the ViewController.swift file from the Project navigator view to load it in the editor.

To start off, copy the following properties declarations (just after the class name):

The data will be downloaded using the URLSessionDownloadTask class, that explains why you declared the task and session properties above. Also, the tableData property will serve as the table view data source and the cache variable is a reference for the cache dictionary the app will use to request cached images -if they exist- before downloading them.

Next, locate the viewDidLoad method and implement the following code just after the super.viewDidLoad call:

The code above will initialise the session task, the table view data source, as well as the cache object. It will also attach a selector to the refresh control to run each time you pull the table view. Let’s go ahead and implement the “refreshTableView” selector.

Basically, the method above will asynchronously request the iTunes search API to return games containing the term “flappy” in their titles or descriptions. Once the data are received, the tableData data source array is filled in with records and the table view is reloaded from the main thread.

To finish off, you will implement two mandatory data source protocol methods to populate the downloaded data, especially images, in the table view rows.

Copy the following code before the class closing bracket:

You just implemented the main part of the app, let me breakdown the code above for better understanding:

// 1: Here, the table view will dequeue a cell for reuse. If no cell is allocated, then the table view will allocate, resize and return a new cell. Next, the current game record in the data source array is extracted in a dictionary object. The game title is set to the cell text label and the cell image is temporary assigned to a placeholder image while waiting for it to download.

// 2: The cache object is a collection-like container, very similar to an NSDictionary instance. Here you used it as a collection of UIImage objects, where the key is the row index (this is very important in order to keep track of the right cache image corresponding to each table cell). So basically, you first check whether there is a cached copy of the current image. If a copy already exists, then you load it in the cell image view.

// 3: If there is no cached copy of the given image, then you asynchronously download it from server.

// 4: Assuming the image is downloaded successfully, the code will switch to the main thread in order to load it to the image view. This is important since all UI tasks should be performed in the main queue and not in a background thread.

// 5: This is the tricky part, it’s important to check whether the cell is visible on screen before updating the image. Otherwise, the image will be reused on each cell while scrolling. The check is a performance saver and mandatory to implement. If the concerned cell is visible, then you just assign the image to cell image view and add it to the cache collection for later use.

You are done with the code. But before running the app, download the placeholder image here and import it to the Xcode project (drag and drop).

Also, disable the ATS (Application Transport Security) for the app in order to be able to perform networking operations. To disable the ATS, select the Info.plist file from the Project navigator view and change it to the following:

Disable App Transport Security

That’s it, run the app, pull to refresh to load the images and enjoy your work :]

As usual, you can download the final project here. Feel free to leave me a comment below, I’d like to hear from you!

About Malek

Malek is a passionate iOS Engineer and Founder of Medigarage Studios, a small mobile games startup. I started my iOS adventures in 2011 and since then I fell in love with it. You can hire me for your project, get in touch by Email to discuss further details. Also, feel free to reach out on Twitter and Google+.

  • Aashish Dhawan

    Using indexPath.row as key to refer object in NSCache will create buga. What if table view dataSource changes? I think we will be referring to wrong image then.

  • Malek_T

    Hi Aashish 🙂
    If the data source is concerned for changes. Then I recommend using some tag types as keys in the cache collection to avoid keys duplicates. Something like incremental number.

  • Pingback: Android & iOS Application Development | Just another My blog Sites site()

  • timbojill

    An newbie IOS developers want to chat/discuss IOS Development ? Email/chat with me at timbojill@gmail.com.

  • Hello. With your code if i close the app and run it again, it downloads the images again. So it saves it in cache only for when the app is open? This is no cache!

  • Malek_T

    Hi, it is cache 🙂
    NSCache has many policies on how it works and retain the right to discard cached data whenever needed to free up system memory.

    Each time the application is entered in background, the cache will be removed. So you can store the cache to NSUserDefaults when it goes to background and restore/reassign it from NSUserDefaults when the app becomes active.

    Quoted from NSCache class documentation:
    “An NSCache object is a collection-like container, or cache, that stores key-value pairs, similar to the NSDictionary class. Developers often incorporate caches to temporarily store objects with transient data that are expensive to create. Reusing these objects can provide performance benefits, because their values do not have to be recalculated. However, the objects are not critical to the application and can be discarded if memory is tight. If discarded, their values will have to be recomputed again when needed.

    While a key-value pair is in the cache, the cache maintains a strong reference to it. A common data type stored in NSCache objects is an object that implements the NSDiscardableContent protocol. Storing this type of object in a cache has benefits, because its content can be discarded when it is not needed anymore, thus saving memory. By default, NSDiscardableContent objects in the cache are automatically removed from the cache if their content is discarded, although this automatic removal policy can be changed. If an NSDiscardableContent object is put into the cache, the cache calls discardContentIfPossible on it upon its removal.”
    More details: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSCache_Class/

  • Do you have any tutorial to do so?

  • raj

    Hello, How can I cache videos downloaded from network? Also, how can I maintain the cache when the app is in foreground and the user goes to a different view and comes back to the view with images/videos? Thanks, Raj

  • Davo Castro

    sweet i made it in swift in 3 ,
    some changes but not big deal, really well explained!!

  • Malek_T

    Always glad to help Davo! Keep it up 🙂

  • Dido Aint

    Pretty cool 😉 Thanks 🙂

  • sorry for VERY late reply! forgot about Disqus!
    Although i learned many things about cache some days after the question to you, you are completely right!