Practical Swift: pages generator - build once, use many

In the real world, at some point, we all have to deal with data that is fetched from a backend service, page by page. It's called paging. Everybody does this! Nothing to be ashamed of ;-) Here is one of the ways in which a Swift pattern (well, not specific to Swift, but visible in Swift) can be used to build a neat solution that allows me to fetch data in chunks and display it in the application.

Paging of list of users

This approach involves Generators. A generator is a state machine that produces new data continuously. So instead of getting all the data at once, I can ask for chunks of data, calling the generator over and over to generate new data. For example, in Swift GeneratorType is a data source for SequenceType.

It's a very simple idea with a neat interface, yet it's powerful. It's all about asking for more data by calling next() - it advances to the next element and returns it, or nil if no next element exists.

generator.next()
Language-swift

GeneratorType protocol is defined as follows:

protocol GeneratorType {
    typealias Element
    mutating func next() -> Element?
}
Language-swift

... this is good for a sequence, but it's not good enough for asynchronous operations. That's why I created a similar Generator, but this one is ready to handle asynchronously generated data (like from network requests).

I'll demonstrate how to build a simple generator, which in conjunction with specialized functions can simply do the job. The job is: fetch data in chunks (pages).

Let's start.

The Generator

At first I defined AsyncGeneratorType protocol

protocol AsyncGeneratorType {
    typealias Element
    typealias Fetch
    mutating func next(fetchNextBatch: Fetch)
}
Language-swift
  • Element describes the type of data to generate
  • Fetch is a closure type, defines a type for data fetching function
  • next(fetchNextBatch: Fetch) is the function to generate the next page of data

Now, let's build a generator that conforms to the AsyncGeneratorType protocol we've just defined, where:

  • Element is an array of generic type T.
  • Fetch is a closure with offset, limit and completion parameters. Completion is called after data is fetched successfully by the function - fetching may be an asynchronous operation.
typealias Element = Array<T>
typealias Fetch = (offset: Int, limit: Int, completion: (result: Element) -> Void) -> Void
Language-swift

Keep the current offset and page size limit:

var offset:Int
let limit: Int

init(offset: Int = 0, limit: Int = 25) {
    self.offset = offset
    self.limit = limit
}
Language-swift

Finally implement a next() function, the core of the paging:

func next(fetchNextBatch: Fetch) {
    fetchNextBatch(offset: self.offset, limit: self.limit) { (items) in
        self.offset += items.count
    }
}
Language-swift

The next() function gets a function (closure) as a parameter. That function will be called every time a new page is fetched from the underlying storage. Because fetching may be an asynchronous operation, after data is fetched, the completion handler has to be called with the resulting items. At this point the generator will update internal state and prepare to serve the next page.

Here's the code of the generator:

class PagingGenerator<T>: AsyncGeneratorType {
    typealias Element = Array<T>
    typealias Fetch = (offset: Int, limit: Int, completion: (result: Element) -> Void) -> Void

    var offset:Int
    let limit: Int

    init(startOffset: Int = 0, limit: Int = 25) {
        self.offset = startOffset
        self.limit = limit
    }

    func next(fetchNextBatch: Fetch) {
        fetchNextBatch(offset: offset, limit: limit) { [unowned self] (items) in
            self.offset += items.count
        }
    }
}
Language-swift

Generate pages of data

What I love about generators is their simple interface. Once defined - it just works. Once I instantiate a generator:

var paging = PagingGenerator<Contact>(startOffset: 0, limit: 25)
Language-swift

I can simply ask for the next page every time I need it:

paging.next(...)
Language-swift

In my case (paging) I need a function that defines actual data fetching. The example function fetchNextBatch() below:

  1. Downloads data from the website
  2. Updates a local list of results
  3. Returns the items back to the completion closure
private func fetchNextBatch(offset: Int, limit: Int, completion: (Array<Contact>) -> Void) -> Void {
    if let remotelyFetched = downloadGithubUsers(offset) {
        self.contacts += remotelyFetched
        completion(remotelyFetched)
    }
}
Language-swift

This is a specialised function that is responsible only for getting the data and returning the result. From now on it's all I have to implement to make the paging work with data from different sources.

Every time I ask for the next page, the generator will fetch it, preprocess it and add the result to my local list. Requesting next page looks like this now:

paging.next(fetchNextBatch)
Language-swift

Specialised functions

Actually, it's not convenient to have fetchNextBatch() responsible for fetching AND updating the UI. Let me change the interface a little to separate these two things into specialised functions. First, update the protocol with an additional parameter onFinish, called after new data is fetched:

func next(fetchNextBatch: Fetch, onFinish: ((Element) -> Void)? = nil) {
    fetchNextBatch(offset: self.offset, limit: self.limit) { (items) in
        onFinish?(items)
        self.offset += items.count
    }
}
Language-swift

Then I can use it to update my UI:

private func updateDataSource(items: Array<Contact>) {
    self.contacts += items
}
Language-swift

by calling it like this:

paging.next(fetchNextBatch, onFinish: updateDataSource)
Language-swift

Result

Here's a final version of PagingGenerator:

protocol AsyncGeneratorType {
    typealias Element
    typealias Fetch
    mutating func next(fetchNextBatch: Fetch, onFinish: ((Element) -> Void)?)
}

/// Generator is the class because struct is captured in asynchronous operations so offset won't update.
class PagingGenerator<T>: AsyncGeneratorType {
    typealias Element = Array<T>
    typealias Fetch = (offset: Int, limit: Int, completion: (result: Element) -> Void) -> Void

    var offset:Int
    let limit: Int

    init(startOffset: Int = 0, limit: Int = 25) {
        self.offset = startOffset
        self.limit = limit
    }

    func next(fetchNextBatch: Fetch, onFinish: ((Element) -> Void)? = nil) {
        fetchNextBatch(offset: offset, limit: limit) { [unowned self] (items) in
            onFinish?(items)
            self.offset += items.count
        }
    }
}
Language-swift

It's not really important which version of Swift this is. What I want to show is the way in which I can deal with data that is downloaded in chunks (pages), in a way that's appropriate for Swift structures.

I can use it to incrementally update a UITableView data source, like this:

class ViewController: UIViewController {
    @IBOutlet var tableView: UITableView!
    private var paging = PagingGenerator<Contact>(startOffset: 0, limit: 25)

    private var contacts = [Contact]() {
        didSet {
            tableView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        paging.next(fetchNextBatch, onFinish: updateDataSource) // first page
    }
}

//MARK: Paging

extension ViewController {
    private func fetchNextBatch(offset: Int, limit: Int, completion: (Array<Contact>) -> Void) -> Void {
        if let remotelyFetched = downloadGithubUsersPage(offset) {
            completion(remotelyFetched)
        }
    }

    private func updateDataSource(items: Array<Contact>) {
        self.contacts += items
    }
}

//MARK: UITableViewDataSource

extension ViewController: UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contacts.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: nil)
        let contact = contacts[indexPath.row]
        cell.textLabel?.text = "\(contact.firstName) \(contact.lastName)"
        return cell
    }
}

//MARK: UITableViewDelegate

extension ViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row == tableView.dataSource!.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1 {
            paging.next(fetchNextBatch, onFinish: updateDataSource)
        }
    }
}
Language-swift

Recap

Generators are nice. Allows easily separate and specialise functions to do one thing right.

I have separated and specialised:

  1. Function to fetch data
  2. Function to update UI after new data arrive
  3. State machine to get next page

Here is a repository with the source code: GeneratedResults-UITableView

PS. Cover photo: https://www.flickr.com/photos/horiavarlan/4332381194