GithubHelp home page GithubHelp logo

vicioux / glitchytable Goto Github PK

View Code? Open in Web Editor NEW

This project forked from pepaslabs/glitchytable

0.0 2.0 0.0 8.29 MB

An exploration of improving UITableView scrolling performance by using asynchronous cell population

License: MIT License

Swift 98.21% Shell 1.79%

glitchytable's Introduction

Populating UITableViewCells Asynchronously to Maintain Scolling Performance

Abstract

In general, the key to keeping the iPhone UI responsive is to avoid blocking the main thread. In the case of UITableView, this boils down to keeping tableView(_:cellForRowAtIndexPath:) as performant as possible.

However, sometimes it is simply not possible to marshall all of the data needed to populate a complex UITableViewCell without causing a drop in framerate. In these cases, it is necessary to switch to an asynchronous strategy for populating UITableViewCells. In this article we explore a trivial example of using this technique.

Demonstrating the Problem

Our first task is to create a simple Xcode project which demonstrates the problem of a slow model leading to laggy scrolling performance.

We start with a model which blocks for 100ms. This simulates the lag induced by excessive disk access, complex CoreData interactions, etc:

class GlitchyModel
{
    func textForIndexPath(indexPath: NSIndexPath) -> String
    {
        NSThread.sleepForTimeInterval(0.1)
        return "\(indexPath.row)"
    }
}

We then hook that model up with some typical boilerplate code in our GlitchyTableViewController implementation:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
    if let cell = cell as? GlitchyTableCell
    {
        _configureCell(cell, atIndexPath: indexPath)
    }
}
    
private func _configureCell(cell: GlitchyTableCell, atIndexPath indexPath: NSIndexPath)
{
    cell.textLabel?.text = model.textForIndexPath(indexPath)
}

(For additional context, see the Xcode project and source code of this solution).

The result is a UITableView with terrible scrolling performance, as shown in this video:

gif 1

(Note: It is difficult to demonstrate the problem in the above in-line gif due to its low framerate. Please click the gif or follow the video link to see a full framerate HTML5 video demonstration of the problem.)

Populating UITableViewCell Asynchronously

Our first attempt at solving this problem is to rewrite _configureCell(_,atIndexPath) such that it dispatches to a background thread to grab the data from the model, then jumps back to the main thread to populate the UITableViewCell:

private func _configureCell(cell: GlitchyTableCell, atIndexPath indexPath: NSIndexPath)
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
        
        let text = self.model.textForIndexPath(indexPath)
        
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            
            cell.textLabel?.text = text
            
        })
    })
}

(See also the Xcode project and source code of this solution).

This change is enough to solve the laggy scrolling performance (as seen in this video):

gif 2

However, we have introduced a bug. If a UITableViewCell scrolls all the way off-screen and gets re-used before the first asynchronous _configureCell call has completed, a second _configureCell call will be queued up in the distpatch queue.

In the case where you have both an extremely laggy model and a user whom is scrolling very aggressively, this can result in many such calls getting queued up. The result is that when the UITableView stops scrolling, the user will see the content of the cells cycle through all of the queued populate operations.

To demonstrate this, we increase the simulated lag in GlitchyModel to 1000ms and scroll very quickly, as seen in this video:

gif 3

Fixing the Queued UITableViewCell Population Bug

To solve this problem, we need to ensure that multiple populate operations aren't allowed to queue up. We can accomplish this by using a serial queue to manage our UITableViewCell populate operations, and ensuring that any outstanding operations are cancelled before we queue up the next operation.

We create a trivial serial queue:

class SerialOperationQueue: NSOperationQueue
{
    override init()
    {
        super.init()
        maxConcurrentOperationCount = 1
    }
}

We then add such a queue to each of our UITableViewCell instances:

class GlitchyTableCell: UITableViewCell
{
    let queue = SerialOperationQueue()
}

Finally, we update our _configureCell implementation to use the operation queue:

private func _configureCell(cell: GlitchyTableCell, atIndexPath indexPath: NSIndexPath)
{
    cell.queue.cancelAllOperations()
    
    let operation: NSBlockOperation = NSBlockOperation()
    operation.addExecutionBlock { [weak operation] () -> Void in
        
        let text = self.model.textForIndexPath(indexPath)
        
        dispatch_sync(dispatch_get_main_queue(), { [weak operation] () -> Void in
            
            if let operation = operation where operation.cancelled { return }
            
            cell.textLabel?.text = text
        })
    }
    
    cell.queue.addOperation(operation)
}

(See also the Xcode project and source code of this solution).

Now, we revisit our extremely problematic model (which simulates 1000ms lag) and verify that it behaves correctly (as seen in this video):

gif 4

Finally, we dial back the simulated lag to 100ms to get a sense of what this would look like in a real-world scenario (as seen in this video):

gif 5

The result is a UITableView UX which is tollerant of 100ms of data model lag.

Conclusion

Populating UITableViewCells asynchronously ensures that your UITableView scrolling performance is decoupled from your data model performance.

glitchytable's People

Contributors

cellularmitosis avatar

Watchers

James Cloos avatar Sergio A. Orozco avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.