Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.88k stars 1.54k forks source link

RFC: Preprocessing API to do expensive work on background before applying batch updates #383

Open rnystrom opened 7 years ago

rnystrom commented 7 years ago

This is a proposal to add an API that makes it easy to do expensive things (e.g. size text, file i/o) on a background queue from within a section controller before an update is applied.

Current situation

If you have expensive work to do before using your section controller, currently your only options are:

We use both of these solutions and still hit 60fps, but it is not easy.

API Design

IGListPreprocessingDelegate

The IGListPreprocessingDelegate will have 2 required methods:

// Called on a background queue. This is where you would do work, sync or async.
- (void)preprocessWithContext:(IGListPreprocessingContext *)context;

// Method called on main once all work is done.
- (void)preprocessingDidFinishWithContext:(IGListPreprocessingContext *)context;

IGListPreprocessingContext

This is object is similar to the UIViewControllerContextTransitioning (should we name it IGListContextPreprocessing?). It's main duties:

Considerations

Main thread affinity

Every public IGListKit API has IGAssertMainQueue() in it and I don't think we should remove any of that so we don't get into threading chaos. That means that when in -preprocessWithContext: you can't do something like [self.collectionContext containerSize]. We should strive to put all necessary background data on each IGListPreprocessingContext object.

Concurrent work

Ideally, preprocessing should all be done concurrently, though we have to wait (non-blocking) until all of the work is finished before continuing. I think we could:

IGListUpdatingDelegate and IGListAdapter APIs

Adding the concurrent step should coalesce other updates (but NOT execute updates!) until the preprocess work and the batch updates are all finished. Getting this working will be a little surgical:

Thoughts?

Curious to hear comments/questions/concerns on this. Specifically, how do you handle expensive sizing/fetching work with IGListKit (or elsewhere)?

Adlai-Holler commented 7 years ago

I love it!

rnystrom commented 7 years ago

Do we want the context to provide information about the change that we're preprocessing for?

Hmmm good question. You mean like if this is a new item vs existing? Or like the actual diff result (move, etc). The latter might be really tricky.

Are we prepared to accept that this will mean the data source and collection view are out-of-sync while preprocessing happens?

If built right, hopefully that shouldn't happen. We'll still have a single place where the data source is updated.

The only risk is that section controllers could change a property or something that effects numberOfItems or w/e. There's all sorts of complications there tho, so hopefully we could encourage people to store data on the context object, then store it on main in preprocessingDidFinishWithContext:.

There's probably more complications that I'm missing.

Do we want to provide an API to wait/perform a synchronous update? Maybe later?

Hmm, maybe later. Probably want to avoid any blocking API though, right? Locking up main would be nasty.

jessesquires commented 7 years ago

So where would this processor hook-in relative to the -objectsForListAdapter: data source method?


We would basically like to have an "asynchronous" version of -objectsForListAdapter: since this is where we map models --> view models.

rnystrom commented 7 years ago

@jessesquires order of ops would probably have to be:

So all the fetching-objects, didUpdateToObject:, sectionControllerForObject: still happens on main.

weyert commented 7 years ago

This sounds really nice. I just learned about dispatch_group_t ;)

jessesquires commented 7 years ago

For anyone wanting to work on this, please see the rough draft at #501.