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 20,000+ Swift developers and enthusiasts who get my weekly updates.