iOS: An Optional vs Empty Data Source in Swift

So here is a common scenario in iOS development. You’re loading a Table View with data from an API. Initially the Table View is blank, since you’re still fetching the data, but very soon (hopefully) the data is fetched and the table view is reloaded as it is meant to be.

The issue in Swift is how to represent the data when it is initially blank.

The Optional Data Source

I personally love the dichotomy of optionals for this. Initially, the data source array is not there, but as I get the data, it is there. Here is an example of a Table View Controller of Taylor Swift’s albums using an optional array of albums as the data source:

import UIKit

class SwiftAlbumsTableViewController: UITableViewController {

    @IBOutlet weak private var activityIndicator: UIActivityIndicatorView!
    
    // THIS IS THE DATA SOURCE
    var albums: [Album]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        fetchAlbums()
    }

    // MARK: - Table view data source

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // have to check if albums exist yet
        return albums?.count ?? 0
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("albumCell", forIndexPath: indexPath)
        
        // it should never go in here 
        // if the albums array is nil
        // but we have to deal with the optional
        // scenario anyway b/c Optionals
        let album = albums?[indexPath.row]
        cell.textLabel?.text = album?.title
        
        return cell
    }
}

// MARK: API Methods
private extension SwiftAlbumsTableViewController {
    
    func fetchAlbums() {
        activityIndicator.startAnimating()
        // fetch data assynchronously via your API
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))),
            dispatch_get_main_queue())
            { [weak self] in
                // mimicking data from the API
                self?.albums = [Album(title: "1989"), Album(title: "Fearless"), Album(title: "Red"), Album(title: "Speak Now")]
                
                // populate data
                self?.tableView.reloadData()
                self?.activityIndicator.stopAnimating()
        }
    }
}

As you can see from this example, the problem with using an optional data source (in our case an optional array of Albums), is that you have to constantly unwrap the optionals. In my case, the data is pretty simple, but you can imagine things getting a bit more messy as a lot more functions in your view controller rely on the data source being there.

The Empty Data Source

This week, @allonsykraken pointed out to me that if instead of using an optional array, I use an empty array, my code would turn out much cleaner:

import UIKit

class SwiftAlbumsTableViewController: UITableViewController {

    @IBOutlet weak private var activityIndicator: UIActivityIndicatorView!
    
    // THIS IS THE DATA SOURCE
    var albums = [Album]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        fetchAlbums()
    }

    // MARK: - Table view data source

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // so clean!
        return albums.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("albumCell", forIndexPath: indexPath)
        
        // no optionals to think of!
        let album = albums[indexPath.row]
        cell.textLabel?.text = album.title
        
        return cell
    }
}

// MARK: API Methods
private extension SwiftAlbumsTableViewController {
    
    func fetchAlbums() {
        activityIndicator.startAnimating()
        // fetch data assynchronously via your API
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))),
            dispatch_get_main_queue())
            { [weak self] in
                // mimicking data from the API
                self?.albums = [Album(title: "1989"), Album(title: "Fearless"), Album(title: "Red"), Album(title: "Speak Now")]
                
                // populate data
                self?.tableView.reloadData()
                self?.activityIndicator.stopAnimating()
        }
    }
}

Since the intent of the table view is to display the data, I have to admit that this is much cleaner to just have the data as empty vs optional.

I was further convinced of this solution when @NSHipster pointed out that many Cocoa APIs in iOS9 “have been modified to return an empty array—semantically these have the same value (i.e., nothing), but a non-optional array is far simpler to work with.”

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Hector Matos

    Woohoo! I made a difference! This is a great post Natasha! Here’s to a big, “we are never ever ever getting back together”, to using optionals as a data source! Lol

  • I have never considered the optional array for this case, and I’m honestly glad now that I’ve always resorted to the empty array. It felt like the clean logical choice from the start since you don’t even have to declare the data source type by just letting Swift infer it.
    Good post nonetheless for all the people out there overusing optionals (don’t get me wrong, I love optionals, but I don’t like trying to have one solution match every scenario).

  • Gabriel Lanata

    I’m sorry I do not agree with this. You get a huge benefit from using an optional, you get three states: Not Loaded, Empty and Has Data. Not loaded is different than empty. You will usually want to show a message while loading (specially if it is loading from the internet) or if there was an error loading and it is easy to see this state by checking the optional.

    • Agree, this post makes a little confuse.
      Empty != Absent.

    • Lars-Jørgen Kristiansen

      I think both approaches are great for different things. If I ever had to check the current state I would use an optional. If not, empty array.

      Because of UIKits imperative approach to view building I find that most of the time I don’t have to check the state.
      I would set the loading state right before loading the data and the error state when the loading fails.

      If I were to use a declarative UI framework like ComponentKit (or I for some other reason had to check the current state), I think optionals are great.

    • Vjosullivan

      The data is neither the database nor the database connection. Using the absence of data to indicate the state of the application or the data connection is very restrictive and fragile, since only one (null) value is available – regardless of the number of reasons as to why there may be no data present. If you need to know if you have a database connection or if you have attempted to load data then these states should be captured in the application, not inferred by the presence of a null optional.

  • I tend to use didSet and avoid optionals – https://www.stuarticus.com/optional-vs-empty-tableviews/

  • This is how I like to think in terms of MVVM. The UITableViewDataSource is actually a view model, while the actual data you get from the server is the data.

    Now in the beginning, you have no model data, so it should be nil but it doesn’t means that you don’t have any view. So, you should have a view-model, and since you have a blank view, you view-model should be blank as well.

    So, a blank array for the view-model while an optional array for the model data.

  • Nuno

    I like your approach. 🙂 I would even go further. If your model doesn’t exist, it may not even make sense to show a view controller. And if that’s the case, I always define an initializer with my model. That way no one will reuse that view controller without having a model to show first. And if the model does exist, and is passed to the initializer empty, it will still free us from the additional optional hassle. 🙂

  • Ashish Porecha

    Good tutorial except for the use of Taylor Swift data as an example. How could you?!?!