Files
Swiftgram/AsyncDisplayKit/Details/ASDataController.h
Huy Nguyen 74c6cca0b6 [ASDataController] Refactor ASDataController (#3017)
* Refactor ASDataController

Check optional methods in ASDataControllerSource

* Reimplement reloadData

* Refactor new code
- 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

* Forgot to call completion block of reloadData :P

* Completion block of -[ASDataController reloadDataWithCompletion:] is nullable

* Data queried from ASCollectionNode and ASTableNode should be in UIKit 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.

* ASDataController shouldn't assume that allocated nodes were also laid out

* Revert "Data queried from ASCollectionNode and ASTableNode should be in UIKit index space"

This reverts commit 7bc977b3808a92f484b297781d0f5b30aa258e17.

* -nodeAtIndexPath: of ASDataController now forces node allocation, with the assumption that clients absolutely need it.
- Revisit this when node virtualization is implemented.

* ASDataController only grab changeSet.completionHandler when needed because it'll be niled out

* Fix ASTableViewTests related to reloadData

* Fix testThatDeletedItemsAreMarkedInvisible in ASCollectionViewTests

* Minor changes in ASCollectionView and ASTableView

* ASCollectionView and ASTableView shouldn't call [super reloadData] before their data controller does anything

* Address comments

* Fuse reloadData into -updateWithChangeSet: of ASDataController

* reloadData shouldn't be called as if it's inside a batch
- 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

* Refactor ASDataControllerDelegate and ASRangeControllerDelegate
- Replace delegate methods in these protocols with -willUpdateWithChangeSet and -didUpdateWithChangeSet.
- ASRangeController, ASCollectionView and ASTableView are simplified because of this.

* Fix mismatch between sorting orders in ASDataController

* Forgot to call completion handler of reload change sets

* Make sure ASCollectionView is compatible with the behavior of UICollectionView'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.

* testSectionIndexHandling of ASTableViewTests should only check visible nodes. Other nodes will be re-measured later.

* ASTableView is not ready until the first layout pass finished

* Address comments

* Bug fixes
2017-02-22 13:50:07 -08:00

225 lines
7.3 KiB
Objective-C

//
// ASDataController.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#pragma once
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASEventLog.h>
#ifdef __cplusplus
#import <vector>
#endif
NS_ASSUME_NONNULL_BEGIN
#if ASEVENTLOG_ENABLE
#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__]
#else
#define ASDataControllerLogEvent(dataController, ...)
#endif
@class ASCellNode;
@class ASDataController;
@class _ASHierarchyChangeSet;
@protocol ASTraitEnvironment;
@protocol ASSectionContext;
typedef NSUInteger ASDataControllerAnimationOptions;
extern NSString * const ASDataControllerRowNodeKind;
extern NSString * const ASCollectionInvalidUpdateException;
/**
Data source for data controller
It will be invoked in the same thread as the api call of ASDataController.
*/
@protocol ASDataControllerSource <NSObject>
/**
Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path.
*/
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath;
/**
The constrained size range for layout.
*/
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/**
Fetch the number of rows in specific section.
*/
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section;
/**
Fetch the number of sections.
*/
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
@optional
- (NSArray<NSString *> *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections;
- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable id<ASSectionContext>)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section;
@end
@protocol ASDataControllerEnvironmentDelegate
- (id<ASTraitEnvironment>)dataControllerEnvironment;
@end
/**
Delegate for notify the data updating of data controller.
These methods will be invoked from main thread right now, but it may be moved to background thread in the future.
*/
@protocol ASDataControllerDelegate <NSObject>
/**
* Called before updating with given change set.
*
* @param changeSet The change set that includes all updates
*/
- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/**
* Called for change set updates.
*
* @param changeSet The change set that includes all updates
*/
- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
@end
/**
* Controller to layout data in background, and managed data updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updating, the corresponding methods in delegate will be called.
*/
@interface ASDataController : NSObject
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
/**
Data source for fetching data info.
*/
@property (nonatomic, weak, readonly) id<ASDataControllerSource> dataSource;
/**
An object that will be included in the backtrace of any update validation exceptions that occur.
*/
@property (nonatomic, weak) id validationErrorSource;
/**
Delegate to notify when data is updated.
*/
@property (nonatomic, weak) id<ASDataControllerDelegate> delegate;
/**
*
*/
@property (nonatomic, weak) id<ASDataControllerEnvironmentDelegate> environmentDelegate;
#ifdef __cplusplus
/**
* Returns the most recently gathered item counts from the data source. If the counts
* have been invalidated, this synchronously queries the data source and saves the result.
*
* This must be called on the main thread.
*/
- (std::vector<NSInteger>)itemCountsFromDataSource;
#endif
/**
* Returns YES if reloadData has been called at least once. Before this point it is
* important to ignore/suppress some operations. For example, inserting a section
* before the initial data load should have no effect.
*
* This must be called on the main thread.
*/
@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled;
#if ASEVENTLOG_ENABLE
/*
* @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead.
*/
@property (nonatomic, strong, readonly) ASEventLog *eventLog;
#endif
/** @name Data Updating */
- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/**
* Re-measures all loaded nodes in the backing store.
*
* @discussion Used to respond to a change in size of the containing view
* (e.g. ASTableView or ASCollectionView after an orientation change).
*/
- (void)relayoutAllNodes;
- (void)waitUntilAllUpdatesAreCommitted;
/** @name Data Querying */
- (NSUInteger)numberOfSections;
- (NSUInteger)numberOfRowsInSection:(NSUInteger)section;
- (nullable ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath;
- (NSUInteger)completedNumberOfSections;
- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section;
- (nullable ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath;
/**
* @return The index path, in the data source's index space, for the given node.
*/
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
/**
* @return The index path, in UIKit's index space, for the given node.
*
* @discussion @c indexPathForNode: is returns an index path in the data source's index space.
* This method is useful for e.g. looking up the cell for a given node.
*/
- (nullable NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode;
/**
* Direct access to the nodes that have completed calculation and layout
*/
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section;
/**
* Immediately move this item. This is called by ASTableView when the user has finished an interactive
* item move and the table view is requesting a model update.
*
* This must be called on the main thread.
*/
- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
@end
NS_ASSUME_NONNULL_END