Merge pull request #303 from facebook/enable_async_data_fetching

Move ASTableView & ASCollectionView data fetching to background thread
This commit is contained in:
Nadine Salter 2015-02-23 15:25:36 -08:00
commit b023cfbb2a
8 changed files with 334 additions and 126 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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];

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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