Move ASTableView & ASCollectionView data fetching to background thread

Conflicts:
	AsyncDisplayKit/ASCollectionView.mm
	AsyncDisplayKit/Details/ASDataController.mm

Conflicts:
	AsyncDisplayKit/ASCollectionView.mm
	AsyncDisplayKit/Details/ASDataController.mm
This commit is contained in:
Li Tan
2015-02-09 16:39:16 -08:00
parent e08975cbfa
commit 2f88ce56a2
6 changed files with 152 additions and 2 deletions

View File

@@ -107,6 +107,26 @@
*/ */
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
@optional
/**
* Indicator to lock the data source for data loading 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)collectionViewLockDataSourceForDataUpdating:(ASCollectionView *)collectionView;
/**
* Indicator to unlock the data source for data loading 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)collectionViewUnlockDataSourceForDataUpdating:(ASCollectionView *)collectionView;
@end @end

View File

@@ -103,6 +103,8 @@ static BOOL _isInterceptedSelector(SEL sel)
NSMutableArray *_batchUpdateBlocks; NSMutableArray *_batchUpdateBlocks;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked;
@end @end
@implementation ASCollectionView @implementation ASCollectionView
@@ -131,6 +133,8 @@ static BOOL _isInterceptedSelector(SEL sel)
_proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate; super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
_asyncDataSourceLocked = NO;
_performingBatchUpdates = NO; _performingBatchUpdates = NO;
_batchUpdateBlocks = [NSMutableArray array]; _batchUpdateBlocks = [NSMutableArray array];
@@ -375,6 +379,26 @@ static BOOL _isInterceptedSelector(SEL sel)
} }
} }
- (void)dataControllerLockDataSourceForDataUpdating
{
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
self.asyncDataSourceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSourceForDataUpdating:)]) {
[_asyncDataSource collectionViewLockDataSourceForDataUpdating:self];
}
}
- (void)dataControllerUnlockDataSourceForDataUpdating
{
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has alredy been unlocked !");
self.asyncDataSourceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSourceForDataUpdating:)]) {
[_asyncDataSource collectionViewUnlockDataSourceForDataUpdating:self];
}
}
#pragma mark - #pragma mark -
#pragma mark ASRangeControllerDelegate. #pragma mark ASRangeControllerDelegate.

View File

@@ -109,6 +109,26 @@
*/ */
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
/**
* Indicator to lock the data source for data loading 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)tableViewLockDataSourceForDataUpdating:(ASTableView *)tableView;
/**
* Indicator to unlock the data source for data loading 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)tableViewUnlockDataSourceForDataUpdating:(ASTableView *)tableView;
@end @end

View File

@@ -112,6 +112,8 @@ static BOOL _isInterceptedSelector(SEL sel)
ASRangeController *_rangeController; ASRangeController *_rangeController;
} }
@property (atomic, assign) BOOL asyncDataSouceLocked;
@end @end
@implementation ASTableView @implementation ASTableView
@@ -137,6 +139,8 @@ static BOOL _isInterceptedSelector(SEL sel)
_proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
super.delegate = (id<UITableViewDelegate>)_proxyDelegate; super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
_asyncDataSouceLocked = NO;
return self; return self;
} }
@@ -425,6 +429,28 @@ static BOOL _isInterceptedSelector(SEL sel)
return CGSizeMake(self.bounds.size.width, FLT_MAX); return CGSizeMake(self.bounds.size.width, FLT_MAX);
} }
- (void)dataControllerLockDataSourceForDataUpdating
{
ASDisplayNodeAssert(!self.asyncDataSouceLocked, @"The data source has already been locked !");
self.asyncDataSouceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSourceForDataUpdating:)]) {
[_asyncDataSource tableViewLockDataSourceForDataUpdating:self];
}
}
- (void)dataControllerUnlockDataSourceForDataUpdating
{
ASDisplayNodeAssert(self.asyncDataSouceLocked, @"The data source has already been unlocked !");
self.asyncDataSouceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSourceForDataUpdating:)]) {
[_asyncDataSource tableViewUnlockDataSourceForDataUpdating:self];
}
}
- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section - (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section
{ {
return [_asyncDataSource tableView:self numberOfRowsInSection:section]; return [_asyncDataSource tableView:self numberOfRowsInSection:section];

View File

@@ -3,6 +3,19 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h> #import <AsyncDisplayKit/ASDealloc2MainObject.h>
/**
Enable the data fetching in async mode.
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 `dataControllerLockDataSourceForDataUpdating`, and unlock it by `dataControllerUnlockDataSourceForDataUpdating`
after the data fetching. The application should not update the data source while the data source is locked.
*/
#define ENABLE_ASYNC_DATA_FETCHING 1
@class ASCellNode; @class ASCellNode;
@class ASDataController; @class ASDataController;
@@ -27,13 +40,23 @@ typedef NSUInteger ASDataControllerAnimationOptions;
/** /**
Fetch the number of rows in specific section. 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. Fetch the number of sections.
*/ */
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController; - (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController;
/**
Lock the data source for data fetching.
*/
- (void)dataControllerLockDataSourceForDataUpdating;
/**
Unlock the data source after data fetching.
*/
- (void)dataControllerUnlockDataSourceForDataUpdating;
@end @end
/** /**

View File

@@ -10,6 +10,18 @@
#import "ASMultidimensionalArrayUtils.h" #import "ASMultidimensionalArrayUtils.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#ifdef ENABLE_ASYNC_DATA_FETCHING
#define BEGIN_DATA_FETCHING \
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ \
[_dataSource dataControllerLockDataSourceForDataUpdating];
#define END_DATA_FETCHING \
[_dataSource dataControllerUnlockDataSourceForDataUpdating]; \
});
#else
#define BEGIN_DATA_FETCHING
#define END_DATA_FETCHING
#endif
#define INSERT_NODES(multidimensionalArray, indexPath, elements, animationOption) \ #define INSERT_NODES(multidimensionalArray, indexPath, elements, animationOption) \
{ \ { \
if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:withAnimationOption:)]) { \ if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:withAnimationOption:)]) { \
@@ -131,7 +143,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Initial Data Loading #pragma mark - Initial Data Loading
- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { - (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption
{
BEGIN_DATA_FETCHING
NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
@@ -150,6 +165,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// insert elements // insert elements
[self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption]; [self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption];
END_DATA_FETCHING
} }
#pragma mark - Data Update #pragma mark - Data Update
@@ -187,6 +204,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
{ {
BEGIN_DATA_FETCHING
__block int nodeTotalCnt = 0; __block int nodeTotalCnt = 0;
NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count]; NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
@@ -222,6 +241,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
}); });
END_DATA_FETCHING
} }
- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
@@ -239,6 +260,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption
{ {
BEGIN_DATA_FETCHING
// We need to keep data query on data source in the calling thread. // We need to keep data query on data source in the calling thread.
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
@@ -264,6 +287,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// reinsert the elements // reinsert the elements
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
}); });
END_DATA_FETCHING
} }
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption
@@ -349,6 +374,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
{ {
BEGIN_DATA_FETCHING
// sort indexPath to avoid messing up the index when inserting in several batches // sort indexPath to avoid messing up the index when inserting in several batches
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
@@ -357,6 +384,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
} }
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
END_DATA_FETCHING
} }
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
@@ -373,6 +402,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
{ {
BEGIN_DATA_FETCHING
// The reloading operation required reloading the data // The reloading operation required reloading the data
// Loading data in the calling thread // Loading data in the calling thread
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
@@ -388,6 +419,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption];
}); });
END_DATA_FETCHING
} }
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption
@@ -407,6 +440,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption - (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption
{ {
BEGIN_DATA_FETCHING
// Fetching data in calling thread // Fetching data in calling thread
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
@@ -446,6 +481,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption];
}); });
END_DATA_FETCHING
} }
#pragma mark - Data Querying #pragma mark - Data Querying