-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[ASDataController] Refactor ASDataController #3017
[ASDataController] Refactor ASDataController #3017
Conversation
a07b4a9 to
0d62525
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an absolutely massive improvement in data controller! I love it, and we're gonna be able to move much much faster from here on out. Left some comments but hopefully we can land ASAP.
AsyncDisplayKit/ASCellNode.h
Outdated
| */ | ||
| @property (nonatomic, assign) BOOL neverShowPlaceholders; | ||
|
|
||
| //TODO shouldn't this be a generic kind (e.g includes ASDataControllerRowNodeKind)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since ASDataControllerRowNodeKind is private, we should expose that as nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we have a common ground that this property should be called kind and exposed as nil for row kind?
On the other hand, can ASDataControllerRowNodeKind possibly be exposed/public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm down for kind or elementKind 👍
| { | ||
| [self endUpdatesCompletion:nil]; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right on!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right on! Removing...
AsyncDisplayKit/ASTableView.mm
Outdated
| } | ||
| //TODO Reimplement _automaticallyAdjustsContentOffset | ||
| // if (_automaticallyAdjustsContentOffset) { | ||
| // [self beginAdjustingContentOffset]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This flag has a very tumultuous history, but we should really strive to support it in every version unless we make it unavailable. If it's absolutely unbearable to implement – even in a rough way – we should be sure to put in the release notes that this flag is not functional in this version.
AsyncDisplayKit/ASTableView.mm
Outdated
| if (!_performingBatchUpdates) { | ||
| [_rangeController updateIfNeeded]; | ||
| } | ||
| // TODO Should reloadData triggers batch fetching? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it should. If I reload data, and I don't have e.g. 2 screenfuls of data ahead of me, I should attempt to batch fetch.
AsyncDisplayKit/ASTableView.mm
Outdated
| }); | ||
|
|
||
| //TODO is _automaticallyAdjustsContentOffset needed for reloadData? | ||
| // if (_automaticallyAdjustsContentOffset) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm good question. I think it should not apply to reloadData because we have to assume that every single item was replaced by something completely different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed!
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WOW I'm glad to see this file gone ⚡️ 🎉 🌴
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super great!!! Thanks @nguyenhuy!
| */ | ||
| - (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray<NSArray<ASCellNode *> *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; | ||
| - (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Very happy to see this argument removed. Let's phrase it didInsertSections:(NSIndexSet *)sectionIndexes since it's clear that we're passing an index set.
| _completedNodeContexts = loadingNodeContexts; | ||
|
|
||
| // Step 3: Now that _completedNodeContexts is ready, call UIKit to update using the original change set. | ||
| [_delegate dataControllerDidReloadData:self]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Formal reloadData!
| _completedNodeContexts = loadingNodeContexts; | ||
|
|
||
| // Step 3: Now that _completedNodeContexts is ready, call UIKit to update using the original change set. | ||
| [self _forwardChangeSetToDelegate:changeSet animated:animated]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How hard would it be to fuse reloadData into updateWithChangeSet: by adding a flag like _ASHierarchyChangeSet.includesReloadData? Much of the internal processing of _ASHierarchyChangeSet would be skipped when that flag is set, and we use it to determine e.g. how to report to the delegate.
That way there's only one implementation of this core update method.
| ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath); | ||
| ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath); | ||
| NSMutableArray<ASNodeContextTwoDimensionalArray *> *deepCopy = [NSMutableArray arrayWithCapacity:originalDict.count]; | ||
| for (ASNodeContextTwoDimensionalArray *sections in originalDict.allValues) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than copying the array with allValues, let's use objectEnumerator here and below.
dc9752b to
d3a8169
Compare
Check optional methods in ASDataControllerSource
- No more new/inserted contexts flag - Encapsulate code shared between reloadData and updateWithChangeSet - Remove dataControllerWillDeleteAllData delegate method - Hierarchy changes no longer needs to conform to NSCopying - Reword TODOs
… index space - This helps to avoid immature node allocation, especially when node virtualization is a thing - However, this means that -reloadDataInitiallyIfNeeded in ASCollectionNode and ASTableNode must wait until all updates are finished.
…in UIKit index space" This reverts commit 7bc977b.
…h the assumption that clients absolutely need it. - Revisit this when node virtualization is implemented.
…cause it'll be niled out
…fore their data controller does anything
- It can't be used in conjuntion with other updates. - Calling it inside a batch update during the initial load can cause data inconsistency thrown by UICollectionView/UITableView
- Replace delegate methods in these protocols with -willUpdateWithChangeSet and -didUpdateWithChangeSet. - ASRangeController, ASCollectionView and ASTableView are simplified because of this.
…ctionView's reloadData - Since UICollectionView's reloadData doesn't requery data source but defers until the next layout pass, we need to wait until then to update range controller and do batch fetching. - `-[ASCollectionView waitUntilAllUpdatesAreCommited]` needs to force a layout pass to make sure everything is ready after it returns.
…e nodes. Other nodes will be re-measured later.
d3a8169 to
0983866
Compare
01423e6 to
1b20117
Compare
|
@Adlai-Holler @appleguy This PR is finally ready for review! As it is getting huge, I'd like to merge this ASAP and work on follow up tasks. Thank you! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely fantastic! I am so so happy to have these changes in. Mostly non-blocking feedback. We can build quite a bit from here! ❤️ @nguyenhuy
| //#define LOG(...) NSLog(__VA_ARGS__) | ||
| #define LOG(...) | ||
| #define LOG(...) NSLog(__VA_ARGS__) | ||
| //#define LOG(...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turn logging back off
| * @param changeSet The change set that includes all updates | ||
| */ | ||
| - (void)rangeController:(ASRangeController *)rangeController didDeleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; | ||
| - (void)rangeController:(ASRangeController * )rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Remove the space
| * @param changeSet The change set that includes all updates | ||
| */ | ||
| - (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray<NSArray<ASCellNode *> *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; | ||
| - (void)dataController:(ASDataController * )dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove stray space
| // Dictionary with each entry is a pair of "kind" key and two dimensional array of node contexts | ||
| #define ASNodeContextTwoDimensionalDictionary NSDictionary<NSString *, ASNodeContextTwoDimensionalArray *> | ||
| // Mutable dictionary with each entry is a pair of "kind" key and two dimensional array of node contexts | ||
| #define ASNodeContextTwoDimensionalMutableDictionary NSMutableDictionary<NSString *, ASNodeContextTwoDimensionalMutableArray *> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
| #pragma mark - Cell Layout | ||
|
|
||
| - (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler | ||
| - (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts batchSize:(NSUInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: When adding new integer variables, let's standardize on NSInteger because it's simpler and the Swift import is way easier.
| } | ||
|
|
||
| ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, _dataSource); | ||
| return ASSizeRangeMake(CGSizeZero, CGSizeZero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: ASSizeRangeZero
| * @param changeSet The change set that includes all updates | ||
| */ | ||
| - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; | ||
| - (void)rangeController:(ASRangeController * )rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the space
|
|
||
| @property (nonatomic, readonly) BOOL completed; | ||
| /// Whether or not changes should be animated. | ||
| @property (nonatomic, readwrite) BOOL animated; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, putting this flag here. My reckoning here is, if any update in the stack is non-animated, the whole update should be non-animated. Would be fine as a follow-up task or discussion, or implement it here.
| if (0 < (_originalDeleteSectionChanges.count + _originalDeleteItemChanges.count | ||
| +_originalInsertSectionChanges.count + _originalInsertItemChanges.count | ||
| + _reloadSectionChanges.count + _reloadItemChanges.count)) { | ||
| ASFailUpdateValidation(@"Attempt to reload data in conjuntion with other updates."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can probably remove this and be lenient on them. Right?
| - (NSMutableArray<NSDictionary *> *)propertiesForDescription | ||
| { | ||
| NSMutableArray<NSDictionary *> *result = [NSMutableArray array]; | ||
| [result addObject:@{ @"_includesReloadData" : @(_includesReloadData) }]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: string should just be "includesReloadData"
b4698a4 to
483f6eb
Compare
|
Here, we, go! |
Author: Thanh Huy Nguyen
This PR has 2 main goals:
ASDataControllerand overhaul the way it processes change sets.ASDataControllernow processes a change set in 3 steps:-reloadDatais much simpler. We finally can destroy everything at once and repopulate the whole data set. And flashes should be gone now ([Discussion] Solutions to eliminate flashes caused by reloadData #2536). Furthermore, it's much easier to support native moves ([ASDataController] "Move" Operations Animate Incorrectly (Delete + Insert) #698, [ASDataController] Move animates using UIKit native behaviour #2868) and reloads (reloadSections(_, withRowAnimation:_) does not reload the section header view #469).ASCollectionDataControllerintoASDataController.ASCollectionDataControllersubclassesASDataControllerto support supplementary views. In order to achieve that, it requires many hooks during the update process. Those hooks add complexity toASDataController. Moreover,ASCollectionDataControllerdoesn't handle supplementary node contexts correctly. It doesn't persistently store those contexts which is important for upcoming features (i.e node virtualization and measurement range).Remaining tasks:
-[ASDataController relayoutAllNodes]and its callees._nodeContextsto be a two dimension array of dictionaries (instead of a dictionary of two dimensional arrays).This PR is still a WIP and contains many TODOs. However, I'd appreciate any early feedbacks and help for testing. Thanks!
Ticket: TextureGroup/Texture#186