Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto diffing section controller #494

Closed
wants to merge 12 commits into from
Closed

Auto diffing section controller #494

wants to merge 12 commits into from

Conversation

rnystrom
Copy link
Contributor

@rnystrom rnystrom commented Feb 15, 2017

Started work on the plane to get this moving since #418 is up and ready to land. We'll likely need to spend some time fleshing out the API of this, and I think I'll split it up into a couple different PRs once ready for review. Putting this up now to get early feedback.

What is this?

This adds an auto-diffing section controller as outlined in #38. There are several key parts:

  • Subclass a new section controller IGListAutoSectionController (naming wip)
  • Connect a data source
  • Implement the data source methods that do 3 things:
    • Given a top-level object, transform it into an array of diffable view models
    • Given a view model, return a cell
    • Given a view model, return a size for a cell
  • A new protocol for the cell IGListBindable so that we can control when the cell is updated w/ the view model.
    • The most important part of this is that it unlocks moving and reloading a cell, which you can't do w/ UICollectionView

TODO

  • Unit test reloadObjects:
  • Add didSelectItemAtIndex: support
  • Verify all headers use FB copyright
  • Create PR for IGListAdapterUpdater changes
  • Create PR for re-entrant updates
    • e.g. calling performBatchAnimated:updates:completion: within an in-progress update block should execute immediately doing this here
  • Create PR for new section controller w/ unit tests
  • Create example
  • Add header docs
  • Optimize test coverage

IGListAdapter

I had to make some changes to IGListAdapterUpdater in order for this to work. The biggest challenge is when the section controller receives a new object, it has to split it up into view models and then apply changes.

However, didUpdateToObject: will be called inside performBatchUpdates:, so we need to diff and fire off the changes on the collection view immediately. A few issues:

Diffing top-level models

One super important piece to getting this right is having top-level (the ones given to IGListAdapter and handed to the SC in didUpdateToObject: always equal itself, even if its values change. I solved this in the unit tests w/ an object that implements diffing like:

@interface IGTestObject: NSObject <IGListDiffable>
@property NSString *key;
@end

@implementation IGTestObject
- (id)diffIdentifier {
  return self.key;
}

- (BOOL)isEqualToDiffableObject:(id)object {
  return [self.key isEqual:[object key]];
}
@end

If you use a traditional isEqualToDiffableObject: (where you check all props/values) you'll end up reloading the section, avoiding all the nice cell animations.

I started making a stock object that does this, something like IGListAutoObject, but its so simple that it's kind of annoying. It's essentially a model box that will probably end up more confusing than having to learn how to diff w/ the auto SC.

Would love thoughts here.

Selection

didSelectItemAtIndex: is totally broken. Options are:

  • Add a new method on the data source protocol
    • Gross b/c that's another empty method for a lot of folks
  • Add another delegate protocol you can optionally wire up

We've discussed splitting off didSelectItemAtIndex: into a selectionDelegate (like the display/scroll/etc delegate) in #184 and elsewhere. That might help, though the view models array is private, so we'd have to expose that or create another delegate API.

How can I help?

Since this is super early, please hold on nit picks, formatting, etc. I'll work on that as we go along. But there are some big things I'd love your thoughts on:

  • The name IGListAutoSectionController. Anything better out there? I thought IGListDiffableSectionController but idk
  • I'm using a data source to split the object into view models, return cells, and handle sizes. I started off passing the SC blocks on init that did the transform (sort of like IGListSingleSectionController). I thought the API was kind of gross though, and you have to subclass the SC anyways.
  • IGListBindable is great, but you can't just use the view model to config the cell. For instance, how do you make the SC the cell delegate? That's why I'm leaving the method to still return a cell, but we will call bind before returning it. Is this ok?
  • Sample project now or later?
  • Let me know if you see some glaring Swift interop issues. I haven't tried it yet.
  • Throw all the crazy test-case scenarios you got at me!

@facebook-github-bot
Copy link
Contributor

@rnystrom updated the pull request - view changes

@heumn
Copy link
Contributor

heumn commented Mar 1, 2017

The name IGListAutoSectionController. Anything better out there? I thought IGListDiffableSectionController but idk

I think it is a good name. It describes what it does pretty well without being too verbose 👌

To get a better feeling for how it would be to use this, it would be awesome with a description on how to test it (in an example project) 💯

@jessesquires
Copy link
Contributor

How about IGListAutoDiffSectionController ?

@heumn
Copy link
Contributor

heumn commented Mar 1, 2017

Yeah, IGListAutoDiffSectionController is actually a better name

@rnystrom
Copy link
Contributor Author

rnystrom commented Mar 1, 2017

@jessesquires @heumn we had some convo in #449 with other ideas:

  • IGListAutoSectionController
  • IGListReactSectionController
  • IGListDiffableSectionController @zhubofei
  • IGListDiffingSectionController @amonshiz
  • IGList(Cell)BindingSectionController
  • IGListViewModelSectionController

I like IGListAutoDiffSectionController too!

@rnystrom
Copy link
Contributor Author

rnystrom commented Mar 4, 2017

Will rebase off of #525 to lighten the load here.

@rnystrom
Copy link
Contributor Author

rnystrom commented Mar 4, 2017

Anyone got any ideas on what we could demo as an example? Something that isn't crazy complicated, but would show off cells updating automatically.

@heumn
Copy link
Contributor

heumn commented Mar 5, 2017

Maybe something like you do in the nested controller example, but use colours and circles to easier track the diffing inside the section?

Can not wait to play with this :)

skjermbilde 2017-03-05 kl 13 03 17

facebook-github-bot pushed a commit that referenced this pull request Mar 7, 2017
Summary:
Batch updates are complicated b/c its unknown when the update block will actually execute. When executing the block, we want to collect inserts/deletes/reloads/moves (item and section). Allow mutations to happen synchronously outside of the update block.

Tracking state will also help with the auto-diff where we need to allow re-entrant updates.

Peeling off a chunk from #494

Issue fixed: #288

- [x] All tests pass. Demo project builds and runs.
- [x] I added tests, an experiment, or detailed why my change isn't tested.
- [x] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
Closes #525

Reviewed By: jessesquires

Differential Revision: D4656463

Pulled By: rnystrom

fbshipit-source-id: 8f4d3dc21b03d595e02ee9ee9664277e8ead0531
@jessesquires
Copy link
Contributor

@rnystrom - can we defer this to 3.1.0 or does this contain breaking changes?

@rnystrom
Copy link
Contributor Author

@jessesquires no breaking changes, we could kick this from 3.0 but I think I'll have this working + example this weekend.

@facebook-github-bot
Copy link
Contributor

@rnystrom updated the pull request - view changes

@facebook-github-bot
Copy link
Contributor

@rnystrom updated the pull request - view changes

@rnystrom
Copy link
Contributor Author

rnystrom commented Mar 11, 2017

Ok, I'm getting super excited about this. Check out the demo (gif below).

I'm about ready to move this from WIP to review. Should be ready tomorrow.

cc @jessesquires @zhubofei @heumn @Sherlouk @PhilCai1993 for feedback

diffing

@heumn
Copy link
Contributor

heumn commented Mar 12, 2017

Initial reaction to this is that you've just saved me hundreds of lines of code and a hack 😂 Looks great

I also postponed "Call mom" until the 25th, and rather "Started cycling" the 20th. No flashing reloading of the sections and things updated just as I wanted 👌 💯

Maybe it could be an idea to add something like this to extend on how powerful this is?

mom

I also keep thinking how you could make it extra clear that the section models has to follow this principle? No ideas apart from the obvious documentation that is needed :)

If not later today then tomorrow I will have more time to start refactoring on my current project and get more hands on experience with this

One super important piece to getting this right is having top-level (the ones given to IGListAdapter and handed to the SC in didUpdateToObject: always equal itself, even if its values change.

@heumn
Copy link
Contributor

heumn commented Mar 12, 2017

Btw, I forgot to that big f***ing thank you for the awesome work you are putting in @rnystrom 🙌

@rnystrom
Copy link
Contributor Author

@heumn great idea! I'm going to add an assert inside -[IGListDiffingSectionController didUpdateToObject:] that checks [object isEqual:self.object] (e.g. two objects should always be equal otherwise a section-reload happens). Also going to load the headers w/ docs and examples.

And nice extension! Maybe we can add that as a follow up?

@heumn
Copy link
Contributor

heumn commented Mar 12, 2017

@heumn great idea! I'm going to add an assert inside -[IGListDiffingSectionController didUpdateToObject:] that checks [object isEqual:self.object] (e.g. two objects should always be equal otherwise a section-reload happens). Also going to load the headers w/ docs and examples.

Perfect 🎉

And nice extension! Maybe we can add that as a follow up?

👌

@rnystrom rnystrom changed the title WIP: Auto diffing section controller Auto diffing section controller Mar 12, 2017
@rnystrom
Copy link
Contributor Author

Importing for internal review!

@facebook-github-bot
Copy link
Contributor

@rnystrom has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@jessesquires
Copy link
Contributor

that calendar view though 😚 👌

@amonshiz
Copy link
Contributor

Early morning review before a run is probably the best kind of review right?

@rnystrom
Copy link
Contributor Author

@amonshiz forget to hit "Submit Review"? 🤔


#pragma mark - Public API

- (void)updateAnimated:(BOOL)animated completion:(void (^)())completion {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this completion block is getting used. And I may have missed it but I don't think it's use is being tested either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eye! Means I need to add a test for this too.

@amonshiz
Copy link
Contributor

@rnystrom yup! reviewing and trying to comment on github at 6:15am right after waking up is clearly not what i'm best at.

@iglistkit-bot
Copy link

iglistkit-bot commented Mar 14, 2017

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@rnystrom
Copy link
Contributor Author

Waddup @iglistkit-bot catching my own mistakes. I'm in love w/ this tool.

@rnystrom
Copy link
Contributor Author

I'm still going back and forth on naming. Now I'm starting to lean towards IGListBindingSectionController b/c:

  • The section controller does the "binding"
  • "Diffing" is an implementation detail

The emphasis of this API should be on the model -> view model -> binding parts, not that it diffs.

What do you all think? @jessesquires @heumn @PhilCai1993 @amonshiz

@amonshiz
Copy link
Contributor

sure? i'm bad at naming.

@jessesquires
Copy link
Contributor

i'll defer to @amonshiz for naming strategies.

andrewnamesthings

@facebook-github-bot
Copy link
Contributor

@rnystrom has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@heumn
Copy link
Contributor

heumn commented Mar 15, 2017

I'm still going back and forth on naming. Now I'm starting to lean towards IGListBindingSectionController b/c:

The section controller does the "binding"
"Diffing" is an implementation detail
The emphasis of this API should be on the model -> view model -> binding parts, not that it diffs.

What do you all think? @jessesquires @heumn @PhilCai1993 @amonshiz

Good reasoning 👌 👍

@jessesquires jessesquires deleted the auto-diff1 branch March 15, 2017 15:32
@PhilCai1993
Copy link
Contributor

It's excited! I wish I could download this commits but how? 😅

@heumn
Copy link
Contributor

heumn commented Mar 17, 2017

It was merged into master, so you can just point you podfile to the master branch :)

@jessesquires
Copy link
Contributor

@PhilCai1993 check out installation guide

https://instagram.github.io/IGListKit/installation.html

@PhilCai1993
Copy link
Contributor

May I have a question here? If in a collectionview with various "Month", since isEqualToDiffableObject: simply returns true, how to reload the entire collectionview?

@rnystrom
Copy link
Contributor Author

@PhilCai1993 covering that in #621 I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants