mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-28 16:42:20 +00:00
Merge pull request #303 from facebook/enable_async_data_fetching
Move ASTableView & ASCollectionView data fetching to background thread
This commit is contained in:
commit
b023cfbb2a
@ -35,6 +35,19 @@
|
||||
*/
|
||||
@property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `AScollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and
|
||||
* `collectionView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `collectionViewLockDataSource`, and unlock it by `collectionViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
@ -108,6 +121,24 @@
|
||||
*/
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Indicator to lock the data source for data fetching in asyn mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*/
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in asyn mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*/
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -101,8 +101,12 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
|
||||
BOOL _performingBatchUpdates;
|
||||
NSMutableArray *_batchUpdateBlocks;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionView
|
||||
@ -111,6 +115,11 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
#pragma mark Lifecycle.
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
return [self initWithFrame:frame collectionViewLayout:layout asyncDataFetching:NO];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
|
||||
return nil;
|
||||
@ -124,13 +133,16 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
_rangeController.delegate = self;
|
||||
_rangeController.layoutController = _layoutController;
|
||||
|
||||
_dataController = [[ASDataController alloc] init];
|
||||
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.dataSource = self;
|
||||
|
||||
_proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
|
||||
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_performingBatchUpdates = NO;
|
||||
_batchUpdateBlocks = [NSMutableArray array];
|
||||
|
||||
@ -375,6 +387,26 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) {
|
||||
[_asyncDataSource collectionViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has alredy been unlocked !");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) {
|
||||
[_asyncDataSource collectionViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASRangeControllerDelegate.
|
||||
|
||||
|
@ -35,6 +35,19 @@
|
||||
*/
|
||||
@property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters;
|
||||
|
||||
/**
|
||||
* initializer.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and
|
||||
* `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
@ -110,6 +123,24 @@
|
||||
*/
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Indicator to lock the data source for data fetching in asyn mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
*/
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in asyn mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
*/
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -110,8 +110,12 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
ASFlowLayoutController *_layoutController;
|
||||
|
||||
ASRangeController *_rangeController;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTableView
|
||||
@ -121,6 +125,12 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
return [self initWithFrame:frame style:style asyncDataFetching:NO];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
|
||||
if (!(self = [super initWithFrame:frame style:style]))
|
||||
return nil;
|
||||
|
||||
@ -130,13 +140,16 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
_rangeController.layoutController = _layoutController;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[ASDataController alloc] init];
|
||||
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
_dataController.dataSource = self;
|
||||
_dataController.delegate = _rangeController;
|
||||
|
||||
_proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
|
||||
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -425,6 +438,28 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
return CGSizeMake(self.bounds.size.width, FLT_MAX);
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked !");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
|
||||
if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) {
|
||||
[_asyncDataSource tableViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked !");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
|
||||
if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) {
|
||||
[_asyncDataSource tableViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section
|
||||
{
|
||||
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
|
||||
|
@ -28,13 +28,23 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
/**
|
||||
Fetch the number of rows in specific section.
|
||||
*/
|
||||
- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section;
|
||||
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section;
|
||||
|
||||
/**
|
||||
Fetch the number of sections.
|
||||
*/
|
||||
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController;
|
||||
|
||||
/**
|
||||
Lock the data source for data fetching.
|
||||
*/
|
||||
- (void)dataControllerLockDataSource;
|
||||
|
||||
/**
|
||||
Unlock the data source after data fetching.
|
||||
*/
|
||||
- (void)dataControllerUnlockDataSource;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
@ -97,6 +107,20 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASDataControllerDelegate> delegate;
|
||||
|
||||
/**
|
||||
* Designated iniailizer.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
|
||||
|
||||
* @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread.
|
||||
* Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread
|
||||
* while allocating cell on main thread, which is frequently reported issue for handing large scale data. On another hand, the application code
|
||||
* will take the responsibility to avoid data inconsistence. Specifically, we will lock the data source through `dataControllerLockDataSource`,
|
||||
* and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while
|
||||
* the data source is locked.
|
||||
*/
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/** @name Initial loading */
|
||||
|
||||
- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption;
|
||||
|
@ -61,6 +61,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
@interface ASDataController () {
|
||||
NSMutableArray *_nodes;
|
||||
NSMutableArray *_pendingBlocks;
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
}
|
||||
|
||||
@property (atomic, assign) NSUInteger batchUpdateCounter;
|
||||
@ -69,11 +70,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
@implementation ASDataController
|
||||
|
||||
- (instancetype)init {
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled {
|
||||
if (self = [super init]) {
|
||||
_nodes = [NSMutableArray array];
|
||||
_pendingBlocks = [NSMutableArray array];
|
||||
_batchUpdateCounter = 0;
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
}
|
||||
|
||||
return self;
|
||||
@ -129,27 +131,41 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)performDataFetchingWithBlock:(dispatch_block_t)block {
|
||||
if (_asyncDataFetchingEnabled) {
|
||||
[_dataSource dataControllerLockDataSource];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
block();
|
||||
[_dataSource dataControllerUnlockDataSource];
|
||||
});
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Initial Data Loading
|
||||
|
||||
- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption {
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
[self performDataFetchingWithBlock:^{
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
|
||||
|
||||
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
|
||||
// insert sections
|
||||
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0];
|
||||
|
||||
// insert sections
|
||||
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0];
|
||||
for (NSUInteger i = 0; i < sectionNum; i++) {
|
||||
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i];
|
||||
|
||||
for (NSUInteger i = 0; i < sectionNum; i++) {
|
||||
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i];
|
||||
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
|
||||
for (NSUInteger j = 0; j < rowNum; j++) {
|
||||
[indexPaths addObject:[indexPath indexPathByAddingIndex:j]];
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
|
||||
for (NSUInteger j = 0; j < rowNum; j++) {
|
||||
[indexPaths addObject:[indexPath indexPathByAddingIndex:j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert elements
|
||||
[self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption];
|
||||
// insert elements
|
||||
[self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption];
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Data Update
|
||||
@ -187,41 +203,43 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
{
|
||||
__block int nodeTotalCnt = 0;
|
||||
NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger cnt = [_dataSource dataController:self rowsInSection:idx];
|
||||
[nodeCounts addObject:@(cnt)];
|
||||
nodeTotalCnt += cnt;
|
||||
}];
|
||||
|
||||
NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:nodeTotalCnt];
|
||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeTotalCnt];
|
||||
|
||||
__block NSUInteger idx = 0;
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) {
|
||||
NSUInteger cnt = [nodeCounts[idx++] unsignedIntegerValue];
|
||||
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIdx];
|
||||
[indexPaths addObject:indexPath];
|
||||
|
||||
ASCellNode *node = [_dataSource dataController:self nodeAtIndexPath:indexPath];
|
||||
[nodes addObject:node];
|
||||
}
|
||||
}];
|
||||
|
||||
dispatch_async([[self class] sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||
for (NSUInteger i = 0; i < indexSet.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
}
|
||||
INSERT_SECTIONS(_nodes , indexSet, sectionArray, animationOption);
|
||||
[self performDataFetchingWithBlock:^{
|
||||
__block int nodeTotalCnt = 0;
|
||||
NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger cnt = [_dataSource dataController:self rowsInSection:idx];
|
||||
[nodeCounts addObject:@(cnt)];
|
||||
nodeTotalCnt += cnt;
|
||||
}];
|
||||
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:nodeTotalCnt];
|
||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeTotalCnt];
|
||||
|
||||
__block NSUInteger idx = 0;
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) {
|
||||
NSUInteger cnt = [nodeCounts[idx++] unsignedIntegerValue];
|
||||
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIdx];
|
||||
[indexPaths addObject:indexPath];
|
||||
|
||||
ASCellNode *node = [_dataSource dataController:self nodeAtIndexPath:indexPath];
|
||||
[nodes addObject:node];
|
||||
}
|
||||
}];
|
||||
|
||||
dispatch_async([[self class] sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||
for (NSUInteger i = 0; i < indexSet.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
}
|
||||
INSERT_SECTIONS(_nodes , indexSet, sectionArray, animationOption);
|
||||
}];
|
||||
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
@ -239,31 +257,33 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
{
|
||||
// We need to keep data query on data source in the calling thread.
|
||||
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
|
||||
[self performDataFetchingWithBlock:^{
|
||||
// We need to keep data query on data source in the calling thread.
|
||||
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
|
||||
|
||||
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx];
|
||||
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx];
|
||||
|
||||
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
|
||||
for (NSUInteger i = 0; i < rowNum; i++) {
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
[updatedIndexPaths addObject:indexPath];
|
||||
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}
|
||||
}];
|
||||
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
// remove elements
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections);
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
|
||||
for (NSUInteger i = 0; i < rowNum; i++) {
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
[updatedIndexPaths addObject:indexPath];
|
||||
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}
|
||||
}];
|
||||
|
||||
// reinsert the elements
|
||||
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
// remove elements
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections);
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
}];
|
||||
|
||||
// reinsert the elements
|
||||
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
@ -349,14 +369,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
{
|
||||
// sort indexPath to avoid messing up the index when inserting in several batches
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) {
|
||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]];
|
||||
}
|
||||
[self performDataFetchingWithBlock:^{
|
||||
// sort indexPath to avoid messing up the index when inserting in several batches
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) {
|
||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]];
|
||||
}
|
||||
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
@ -373,21 +395,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
{
|
||||
// The reloading operation required reloading the data
|
||||
// Loading data in the calling thread
|
||||
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
[indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}];
|
||||
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
[self performDataFetchingWithBlock:^{
|
||||
// The reloading operation required reloading the data
|
||||
// Loading data in the calling thread
|
||||
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
[indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}];
|
||||
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
}];
|
||||
|
||||
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
@ -407,45 +431,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
||||
{
|
||||
// Fetching data in calling thread
|
||||
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
|
||||
[self performDataFetchingWithBlock:^{
|
||||
// Fetching data in calling thread
|
||||
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
|
||||
|
||||
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
|
||||
for (NSUInteger i = 0; i < sectionNum; i++) {
|
||||
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
|
||||
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
|
||||
for (NSUInteger i = 0; i < sectionNum; i++) {
|
||||
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
|
||||
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
|
||||
for (NSUInteger j = 0; j < rowNum; j++) {
|
||||
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
|
||||
[updatedIndexPaths addObject:indexPath];
|
||||
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes);
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)];
|
||||
DELETE_SECTIONS(_nodes, indexSet, animationOption);
|
||||
|
||||
|
||||
// Insert section
|
||||
|
||||
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum];
|
||||
for (int i = 0; i < sectionNum; i++) {
|
||||
[sections addObject:[[NSMutableArray alloc] init]];
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
|
||||
for (NSUInteger j = 0; j < rowNum; j++) {
|
||||
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
|
||||
[updatedIndexPaths addObject:indexPath];
|
||||
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||
}
|
||||
}
|
||||
|
||||
INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections, animationOption);
|
||||
dispatch_async([ASDataController sizingQueue], ^{
|
||||
[self syncUpdateDataWithBlock:^{
|
||||
|
||||
}];
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes);
|
||||
DELETE_NODES(_nodes, indexPaths, animationOption);
|
||||
|
||||
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)];
|
||||
DELETE_SECTIONS(_nodes, indexSet, animationOption);
|
||||
|
||||
|
||||
// Insert section
|
||||
|
||||
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum];
|
||||
for (int i = 0; i < sectionNum; i++) {
|
||||
[sections addObject:[[NSMutableArray alloc] init]];
|
||||
}
|
||||
|
||||
INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections, animationOption);
|
||||
|
||||
}];
|
||||
|
||||
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Data Querying
|
||||
|
@ -34,7 +34,7 @@
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
|
||||
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES];
|
||||
_collectionView.asyncDataSource = self;
|
||||
_collectionView.asyncDelegate = self;
|
||||
_collectionView.backgroundColor = [UIColor whiteColor];
|
||||
@ -78,4 +78,13 @@
|
||||
return 300;
|
||||
}
|
||||
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView {
|
||||
// lock the data source
|
||||
// The data source should not be change until it is unlocked.
|
||||
}
|
||||
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView {
|
||||
// unlock the data source to enable data source updating.
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -12,6 +12,7 @@
|
||||
#import "ViewController.h"
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#import "BlurbNode.h"
|
||||
#import "KittenNode.h"
|
||||
@ -25,8 +26,13 @@ static const NSInteger kLitterSize = 20;
|
||||
|
||||
// array of boxed CGSizes corresponding to placekitten kittens
|
||||
NSArray *_kittenDataSource;
|
||||
|
||||
BOOL _dataSourceLocked;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSArray *kittenDataSource;
|
||||
@property (atomic, assign) BOOL dataSourceLocked;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -40,7 +46,7 @@ static const NSInteger kLitterSize = 20;
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // KittenNode has its own separator
|
||||
_tableView.asyncDataSource = self;
|
||||
_tableView.asyncDelegate = self;
|
||||
@ -59,6 +65,12 @@ static const NSInteger kLitterSize = 20;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setKittenDataSource:(NSArray *)kittenDataSource {
|
||||
ASDisplayNodeAssert(!self.dataSourceLocked, @"Could not update data source when it is locked !");
|
||||
|
||||
_kittenDataSource = kittenDataSource;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
@ -105,4 +117,12 @@ static const NSInteger kLitterSize = 20;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView {
|
||||
self.dataSourceLocked = YES;
|
||||
}
|
||||
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView {
|
||||
self.dataSourceLocked = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
Loading…
x
Reference in New Issue
Block a user