Summary:
Using blocks can be nice, but here it causes a couple problems:
* The order of execution is a bit hard to follow. For example, in `-begin`, the very first thing we see defined is `executeCompletionBlocks` which is the last thing actually called. `IGListReloadTransaction` isn't too complex, but it gets even harder to follow in `IGListBatchUpdateTransaction`.
* It makes crash and assert reports hard to understand, because the stack includes mostly block names.
So lets turn blocks into methods.
Reviewed By: patters
Differential Revision: D23151918
fbshipit-source-id: 3ac4c77e9978c2700fd6744f084f624d27f0d300
Summary:
Lets move things to the right transaction:
* `performBatchUpdate` path to `IGListBatchUpdateTransaction`
* `reloadData` path to `IGListReloadTransaction`
Reviewed By: patters
Differential Revision: D23145770
fbshipit-source-id: e80fc05d2783e165354a147453083b449c92a61c
Summary: Now, lets move pending changes to a separate object `IGListUpdateTransactionBuilder`, which can we discarded once the update actually starts.
Reviewed By: patters
Differential Revision: D23145774
fbshipit-source-id: e12de178de33497f476972a9ad89ebb4e8d413ab
Summary:
Before adding `transactions`, lets clarify what information is collected before vs during the update. We can split apart `IGListBatchUpdates`.
* Before the update
* Collect the update blocks in `itemUpdateBlocks` which will be executed later during the update.
* Collect completion blocks in the same `completionBlocks` list as the other updates.
* During the update
* Collect item updates in `IGListItemUpdatesCollector`, like inserts and deletes.
* Collect new completion blocks (in case a section-controller schedules an update in -didUpdateToObject) in a separete list `inUpdateCompletionBlocks`
Reviewed By: patters
Differential Revision: D23145778
fbshipit-source-id: cae2c0689ac1ef0d93e3c04856b0495856e305b6
Summary:
Lets implement `performExperimentalUpdateAnimated`. There's a couple of things happening:
* We're now using the `IGListTransitionData` container object
* We don't need `pendingTransitionToObjects` anymore because we query the `fromObjects` just before performing the diffing.
* We should update the unit tests to use `performExperimentalUpdateAnimated` since the regular `performUpdateAnimated` will now fail.
Reviewed By: patters
Differential Revision: D23145781
fbshipit-source-id: 0b896e918241e709c5eb23f56a6b7323521a2222
Summary:
There's a few issues with `IGListAdapterUpdater` which would be difficult to safely fix "in place", so lets create a duplicate updater that we can test separatly.
Ugh, copy paste?
* The alternative would be to change `IGListAdapterUpdater` directly and have lots of `if/else` branching. I've tried it and it's hard to follow and easy to break. By creating a separate class, we do run the risk of having 2 diverging updaters, but I think it's worth the risk since it rarely gets changed. I'll try to ship (or burn) this new updater quickly.
Why duplicate `IGListAdapterUpdater` rather then starting from scratch?
* I want to take advantage of the existing unit tests. I can make small incremental changes and make sure the tests still pass.
* There's a lot going on in `IGListAdapterUpdater` and it's easier to refactor it by moving things around, rather then writing it from nothing.
Why does `IGListAdapterUpdater` need a clean up?
* Check out the first diff in the stack or task T74605897.
Reviewed By: patters
Differential Revision: D23145777
fbshipit-source-id: 360e980a89a5681ce38d1b5f6f1ca035eb1eb195
Summary:
For diffing, `IGListAdapterUpdater` uses the full `fromObjects` instead of `validObjects`, which can be different if we're missing `IGListSectionController`. The diffing results won't match what the `IGListAdapter` tells the `UICollectionView`, so we can crash.
To fix this, lets try to generate the `IGListSectionController` before performing the diffing, and keep them in a container object (`IGListTransitionData`) alonside the `fromObjects` and `toObjects`. This might also unblock other cool features in the future, like giving `IGListSectionControllers` time for background work, but that's for another day.
Since this change is relatively large, lets create a temporary protocol `IGListUpdatingDelegateExperimental` which will have a slightly different API from `IGListUpdatingDelegate`. When/if the new implementation ships, we can merge both protocols.
Why not edit `IGListAdapterUpdater` directly?
* I don't want to mess up exiting updaters just yet.
Reviewed By: patters
Differential Revision: D23145776
fbshipit-source-id: 21642cafa64e2a26a173e2ba683ef5c6cede17d7
Summary: Create temporary protocol for `IGListAdapterUpdater` to test a different implementation. The idea is to ship (or burn) the new implementation before the next 5.0.0 version release.
Reviewed By: patters
Differential Revision: D23145772
fbshipit-source-id: ede312c4a1289a34f73c14415a9bca40033fe925
Summary: We pass `experiments` to places that don't need it, so lets clean that up.
Reviewed By: patters
Differential Revision: D23145782
fbshipit-source-id: affcfe0f38d1b8e544940af89e5692dbdc36dd76
Summary:
On `IGListAdapater` data update:
1) Pass the capacity count, so that arrays don't have to re-size.
2) Avoid using a set, so that we don't need to deal with hashes and equality. The updater should have dealt with duplicates already.
Reviewed By: patters
Differential Revision: D23145771
fbshipit-source-id: 2ed93231e15ddcd66cfe4d1f7384c563c77caa8e
Summary:
Issue: Invalidating the layout of a `IGListSectionController` that's been removed from the map crashes, because we're passing `NSNotFound` to `[UICollectionView numberOfItemsInSection:...]`. This can easily happen if the section-controller invalidates its layout in a `performBatchAnimated` completion block, but the associated object was deleted during the update or after a full reload.
Fix: Like the rest of `IGListAdapter`, lets check for a valid `NSInteger` and bail early if we don't have it.
Reviewed By: mariajayne
Differential Revision: D22588010
fbshipit-source-id: bf0c26f313795bb10682ef6b660d6ea8a7ce9f1e
Summary: Wrap `[UICollectionView performBatchUpdates ...]` so that in case it crashes, the first app symbol will not be a block. A block name includes the line number, which means if you change the block line number, it will be categorized as a different crash. This makes tracking crashes across multiple app-versions a pain.
Differential Revision: D22398750
fbshipit-source-id: 90643e7eafe76b07e7022111f183e4734fd8a347
Summary: To prepare testing `Foundation` vs `IGListKit` diffing, lets measure how long it takes.
Reviewed By: Haud
Differential Revision: D22295546
fbshipit-source-id: 8023717f66ea68cbc24981272e8c134dd893274a
Summary: This header refers to `UICollectionViewCell` but does not import UIKit.
Reviewed By: candance
Differential Revision: D22340609
fbshipit-source-id: 8d03a48d363ecf3b00b29f89828d83ab44615fd4
Summary: The duration of an update depends a lot of if it's animated, so lets pass that info to the delegate.
Reviewed By: lorixx
Differential Revision: D22265405
fbshipit-source-id: 4d164447384b84eb6f41c7f8365d0b28670e1372
Summary: We call `[collectionView layoutIfNeeded]` before performing updates, but it's not clear why. It leads to some strange animation issues when performing updates while scrolling. So lets try to skip it?
Reviewed By: Haud
Differential Revision: D22244053
fbshipit-source-id: 0c01a73860ebc16f430fed04d5c29a5bde038286
Summary: This experiment shipped so we can remove it. We still need a way to disable it with `allowsReloadingOnTooManyUpdates` in case we don't want to resign the first responder on large updates.
Reviewed By: Haud
Differential Revision: D22219238
fbshipit-source-id: 98e78f3ed040809db6c4b4c8da8fd0e976aca0a2
Summary: If we fallback to `reloadData`, we don't queue the next update. That means if you call `performUpdate` multiple times quickly, there's a chance that the last update doesn't take effect.
Reviewed By: Haud
Differential Revision: D22219237
fbshipit-source-id: 167e3428859a0194f8f8c57624504edd531480df
Summary:
Issue: There's a couple of situations where `IGListAdapterUpdater` updates the `collectionView` without notifying the `IGListAdapterUpdater`.
* If we call `reloadDataFallback()` because of a missing window, there's no delegate calls.
* If we call `reloadDataFallback()` because of too many diff updates, we call `willPerformBatchUpdatesWithCollectionView` but not `didPerformBatchUpdates`.
Fix: Lets clean up the delegate calls
* If we `[UICollectioView performBatchUpdates:]` lets call `willPerformBatchUpdatesWithCollectionView` & `didPerformBatchUpdates`
* If we fallback to `[UICollectionView reloadData]` lets call `willReloadDataWithCollectionView` & `didReloadDataWithCollectionView`
* If we fallback to doing nothing, lets call `didFinishWithoutUpdates`
Reviewed By: Haud
Differential Revision: D22219236
fbshipit-source-id: 1afb4df8dbbaf4725424027bb52c1a5cc5100b30
Summary: Actually this won't work and causes even more crashes. For example, if we reload cell 0 and insert at 0, we end up with just a single delete & insert at 0, which causes an inconsistency exception. `IGListApplyUpdatesToCollectionView` already takes care of avoiding multiple reloads of the same `NSIndexPath` by using a `NSMutableSet` when collecting the `reloadDeletePaths`.
Reviewed By: apadalko
Differential Revision: D21822502
fbshipit-source-id: c2382951ffe013f82c1de2492286d2911f40444d
Summary: Ship this experiment. `IGListCollectionViewLayout` should get the section/index counts via `UICollectionView` to stay in sync, instead of the `dataSource`.
Reviewed By: apadalko
Differential Revision: D21789871
fbshipit-source-id: 9fe069bd793b36680cd8562e69c7367d69a11ec7
Summary:
= Context =
Thanks to the awesome Comparison View analysis, I am able to pinpoint where the scroll perf regression is for the single section inline update QE.
CV2 link: https://fburl.com/cv/fttkv99b
Also see from the graph that the these call stacks are pretty stat-sig,
https://pxl.cl/14D9M
And from the following stack stats, it's pretty clear that the heaviest one is calling from `-[IGListBindingSingleSectionController didUpdateToObject:]`, Approx. Frame Percentage is 24% to 1% compared to Control test group for my QE.
https://pxl.cl/14Db0
= Root cause =
Basically in my new QE, whenever IGListKit update, the -didUpdateToObject: is called on every single IGListSectionController, which in our case, we would call -[-configureCell:withViewModel:] all every single cells. And it's actually pretty expensive for any cell configuration:
https://pxl.cl/14DbZ
In control group, we never trigger any cell "Re-binding" thus no heavy impact. However the way control group works is, that we always trigger another cell creation and replace with the existing one. IGListKit would still check -isEqualToDiffable: to decide whether the current cell needs a reload, the logic is inside IGListAdapterUpdater.
= Solution =
It's easy, just do a simple `-isEqualtoDiffable:` check before updating the _item, inside `[-IGListBindingSingleSectionController didUpdateToObject:]` method.
Differential Revision: D20909174
fbshipit-source-id: 59d57fc8ddda7210cd1ae333942a345025cf1ee3
Summary: This allows us to potentially enable `-Wundef` in the future. Behavior is the same, undefined macros are false.
Differential Revision: D20906603
fbshipit-source-id: d5798861f0c9dfcd7a87936d575621d52c0ef7c7
Summary: We add a boolean isDisplayingCell method to check the existence of displayingCell in IGListBindingSingleSectionController
Reviewed By: chritto
Differential Revision: D20649871
fbshipit-source-id: f626cfdedff907604514c485d11362cd9ba12b99
Summary:
A common cause of `NSInternalInconsistencyException` is when a section controller tries to access a cell or modify the `UICollectionView` in `-didUpdateToObject`. If the collection view doesn't have data yet, this will kick off the regular calls (ex: `-numberOfSectionsInCollectionView`, `-numberOfItemsInSection`), which won't have the right information yet because the `IGListAdpater` is in the middle of updating the object list. Eventually, this can lead to a crash, which are hard to debug because the callstack doesn't include the call that caused the premature update.
Lets add an assert to catch when this happens.
Reviewed By: candance
Differential Revision: D20527372
fbshipit-source-id: 4ad0b2d89e5b126d6ae888a5c3b1d980945b7a4c
Summary:
**Context**
Recently an IGListKit experiment was introduced, `IGListExperimentPerformUpdatesWithoutDeferringCATransactionCommit`. When enabled, we opt to no longer defer a call to `[CATransaction commit]`, because it could feasibly end a different transaction than intended. In practice, this was leading to issues with `UIViewPropertyAnimator`, where deffered commits were ending in-progress animators.
**This Change**
The results we have seen from enabling this fix show no changes to performance and stability, so this seems safe to roll out. This change removes the experiment, and enables the new, non-deffered behavior.
Reviewed By: lorixx
Differential Revision: D20120169
fbshipit-source-id: 0473652020a250d67b02b860fb74c73e43615aef
Summary:
We were using the iVar version of the `_displayDelegate`, but turned out a lot of product callsite just manually override the `displayDelegate` instead of use `self.displayDelegate = self`.
Let's use the self.displayDelegate instead.
Reviewed By: bdotdub
Differential Revision: D20037298
fbshipit-source-id: fa860adfa88cd088f39718279983acd32e90b478
Summary:
There is a KP in IGListBindingSingleSectionController that we need to override the `displayDelegate` in order to track the displayingCell for performant update, when the section controller is not visible.
With the newly refactoring in how IGListSectionController handles the displaying events, IGListBindingSingleSectionController can now override the willDisplay/endDisplay calls without manually adding itself as the `displayDelegate`, the behavior works as before.
Differential Revision: D19973670
fbshipit-source-id: 1f9fec1bf88aa8755c163c636ebb49b377e6873c
Summary:
Previously, IGListDisplayHandler took over the sectionController's displayDelegate and invoke directly.
We have requirement in other cases where we want to control the displayDelegate invokation, e.t. Subclass of IGListSectionController can listen to the willDisplay/endDisplay events and do other processing.
Moving the displayDelegate invokation inside IGListSectionController would allow us to have better control on the invokation timing.
Differential Revision: D19973669
fbshipit-source-id: 33d23bc7efb235d2e1ed99737200e56bb320b678
Summary:
When a section/cell is reloaded, UICollectionView actually calls:
1/ WillDisplay (new cell)
2/ DidEndDisplay (old cell)
So if we set `_displayingCell` in willDisplay call, then set it to nil in didEndDisplay, we would just have a nil cell. Thus we need to make sure the cell in `didEndDisplay` match with the previous `_displayCell` to avoid nil-ing ourselves out.
Reviewed By: alanzeino
Differential Revision: D19906734
fbshipit-source-id: 5217908ae99d488a2af5ff3f90ef8e021774acda
Summary:
Pull Request resolved: https://github.com/Instagram/IGListKit/pull/1418
This assert causes crashes on debug builds. While it's known that this algorithm is slow with a large number of items to work through, it doesn't seem to have been an issue for most when working with IGListKit. Removing this assertion to avoid crashes for others.
Reviewed By: bdotdub
Differential Revision: D19251924
fbshipit-source-id: c0e78d3963544b949bed823bdac9b39529ca5f9f
Summary:
Originally we want to update the cell when the SectionController is asked to update to a new value, that's pretty much what the experiment `singleItemSectionUpdates` is for inside `IGListAdapterUpdater`.
However, we should not request any cell during the initial setup, otherwise it would trigger cell to render in a wrong time, in particular we shouldn't call
```
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
```
as the data source set up is not ready, and it can get crashy to compare the initial dataSource(nil) to the valid data source(IGListAdapter), which doesn't make sense.
So tl;dr is do not access cell during the initial data source setup. We might need to audit other callsite that might have violate this invariant.
Solution here is to rely on the willDisplay/endDisplay API from `IGListDisplayDelegate` so that we can set/unset the displaying cell here before we configure the cell with the updated data. This change is also safe as it's protected by `_enabledCellConfigurationDuringUpdate` which defaults to be NO.
= Facebook =
Also currently the `singleItemSectionUpdates` is only setup for Inbox thread-bump useage for now. So I added a `enabledCellConfigurationDuringUpdate` to properly gate it behind QE so that we have a safer exit here.
Reviewed By: maxolls
Differential Revision: D19332040
fbshipit-source-id: cbca7a63b0486c053ceabe759ce316f5d841fef5
Summary:
The `cellClass` parameter was still documented, but no longer present in the method signature.
Pull Request resolved: https://github.com/Instagram/IGListKit/pull/1412
Reviewed By: lorixx
Differential Revision: D19178421
Pulled By: iperry90
fbshipit-source-id: 17e69db2c5d52581bfd205b5557c6564cf0e5673
Summary:
## Changes in this pull request
Initial suggested implementation for https://github.com/Instagram/IGListKit/issues/1387.
### Checklist
- [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.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Pull Request resolved: https://github.com/Instagram/IGListKit/pull/1388
Reviewed By: lorixx
Differential Revision: D18687735
Pulled By: natestedman
fbshipit-source-id: f9cc70ced3f788771fd3f0443b56befbedb04166
Summary:
**Context**
When performing non-animated updates, `IGListKit` does so by beginning a `CATransaction` before `performBatchUpdates:`, and ending it in the completion block. While this disables animations for the collection view updates, it is feasible that some other changes might be bundled into the same transaction, given that we have no guarantee of the completion block being called inline.
It seems that this is happening in practice and occasionally disrupting `UIViewPropertyAnimator` animations, forcing the completion immediately.
**This Change**
To fix this issue, we can commit the transaction inline. Here we use `performWithoutAnimation:`, although it's possible that this is just a wrapper around `CATransaction` calls anyway.
Because the change may have unknown side effects, this change is gated by an experiment.
Reviewed By: maxolls
Differential Revision: D19150307
fbshipit-source-id: f7cff46ac7141609f42caafaf69bad71a980ad29
Summary:
= Problem =
Most of the UICollectionView/List UI only needs to deal with 1 dimensional array of data. The current N section N item setup is a big more complicated for most use case who only deals with 1 dimensional array.
In fact, the IGListDiff algorithm works pretty with 1 dimensional array. Then inside `IGListAdapterUpdater`, we do a lot changes to ensure UICollectionVie do not crash:
1. Convert section moves -> delete+insert;
2. Convert section reload -> delete+insert;
This results in the animation limitation for the UI updates, we lose the move animation support by UICollectionView and the updates for delete+insert does not look great.
= Solution =
Since direct inbox or direct message list view only uses N section, with single item setup.
We don't need to do crazy changes like changing moves->delete+insert, and use -reloadSections etc. for updates.
We can just use whatever the IGListDiffingResult returns and be done with it.
More context, see my post and the Better Animation part I listed in the group: https://fb.workplace.com/groups/iglistkit/permalink/1009885999357045/
Reviewed By: iperry90
Differential Revision: D18856355
fbshipit-source-id: 50cf93a6903f0c86c44e5e1289384efe8db9acc1
Summary:
= Problem =
Most of the UICollectionView/List UI only needs to deal with 1 dimensional array of data. The current N section N item setup is a big more complicated for most use case who only deals with 1 dimensional array.
In fact, the IGListDiff algorithm works pretty with 1 dimensional array. Then inside `IGListAdapterUpdater`, we do a lot changes to ensure UICollectionVie do not crash:
1. Convert section moves -> delete+insert;
2. Convert section reload -> delete+insert;
This results in the animation limitation for the UI updates, we lose the move animation support by UICollectionView and the updates for delete+insert does not look great.
= Solution =
Hence I am proposing to use simple **Single Item** Section Controller, and we can apply the diffing result directly to the UICollectionView, which is much simpler.
However, for the inline section update, we need to support update at the right timing: inside -didUpdateObject:, and now we want to get back the existing cell, and apply data to it.
Ideally, `IGListSingleSectionController` is the right call but `IGListSingleSectionController` is not subclassable and we would also need to change the way -didUpdateObject: works in that class.
Hence I am building this `IGListBindingSingleSectionController`, which only contains single item, and it can update/bind the cell whenever item is updated.
Reviewed By: iperry90
Differential Revision: D18942974
fbshipit-source-id: 539fbe2b72691b649e3ae3d8ed725006bc54b705
Summary: I want to reuse this collection view update code in next diff, let's move it to a Helper file to support code sharing.
Reviewed By: calimarkus, iperry90
Differential Revision: D18714061
fbshipit-source-id: a85e8576226276fcab90ea1d2ed23aac5501aa17
Summary:
…uld show empty view
## Changes in this pull request
Basically we can use the existing _itemCountIsZero like all the other usage, and it's faster since it stopped early, compared to before we loop through all the sectionController.numberOfItems and sum up.
### Checklist
- [x] All tests pass. Demo project builds and runs.
- [ ] I added tests, an experiment, or detailed why my change isn't tested.
- [ ] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Pull Request resolved: https://github.com/Instagram/IGListKit/pull/1400
Differential Revision: D18687637
Pulled By: lorixx
fbshipit-source-id: bcdc57ba43b86cd0ed639ffaab9ae0493fd2f844