From 2f88ce56a24de5a53acabff5d677718244f47686 Mon Sep 17 00:00:00 2001 From: Li Tan Date: Mon, 9 Feb 2015 16:39:16 -0800 Subject: [PATCH 1/3] Move ASTableView & ASCollectionView data fetching to background thread Conflicts: AsyncDisplayKit/ASCollectionView.mm AsyncDisplayKit/Details/ASDataController.mm Conflicts: AsyncDisplayKit/ASCollectionView.mm AsyncDisplayKit/Details/ASDataController.mm --- AsyncDisplayKit/ASCollectionView.h | 20 +++++++++++ AsyncDisplayKit/ASCollectionView.mm | 24 +++++++++++++ AsyncDisplayKit/ASTableView.h | 20 +++++++++++ AsyncDisplayKit/ASTableView.mm | 26 ++++++++++++++ AsyncDisplayKit/Details/ASDataController.h | 25 ++++++++++++- AsyncDisplayKit/Details/ASDataController.mm | 39 ++++++++++++++++++++- 6 files changed, 152 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 9f83a3046c..d65a900c60 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -107,6 +107,26 @@ */ - (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 diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 36cc0e2f60..ddd5ac254e 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -103,6 +103,8 @@ static BOOL _isInterceptedSelector(SEL sel) NSMutableArray *_batchUpdateBlocks; } +@property (atomic, assign) BOOL asyncDataSourceLocked; + @end @implementation ASCollectionView @@ -131,6 +133,8 @@ static BOOL _isInterceptedSelector(SEL sel) _proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; super.delegate = (id)_proxyDelegate; + _asyncDataSourceLocked = NO; + _performingBatchUpdates = NO; _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 ASRangeControllerDelegate. diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index faf1b29cc4..e163bd69d1 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -109,6 +109,26 @@ */ - (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 diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 4169dc5f8a..2c2c43cf80 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -112,6 +112,8 @@ static BOOL _isInterceptedSelector(SEL sel) ASRangeController *_rangeController; } +@property (atomic, assign) BOOL asyncDataSouceLocked; + @end @implementation ASTableView @@ -137,6 +139,8 @@ static BOOL _isInterceptedSelector(SEL sel) _proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; super.delegate = (id)_proxyDelegate; + _asyncDataSouceLocked = NO; + return self; } @@ -425,6 +429,28 @@ static BOOL _isInterceptedSelector(SEL sel) 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 { return [_asyncDataSource tableView:self numberOfRowsInSection:section]; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index f271066d44..f493eee28d 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -3,6 +3,19 @@ #import #import +/** + 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 ASDataController; @@ -27,13 +40,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)dataControllerLockDataSourceForDataUpdating; + +/** + Unlock the data source after data fetching. + */ +- (void)dataControllerUnlockDataSourceForDataUpdating; + @end /** diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 483b5d29c2..78356e94c5 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -10,6 +10,18 @@ #import "ASMultidimensionalArrayUtils.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) \ { \ if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:withAnimationOption:)]) { \ @@ -131,7 +143,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Initial Data Loading -- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { +- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption +{ + BEGIN_DATA_FETCHING + NSMutableArray *indexPaths = [NSMutableArray array]; NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; @@ -150,6 +165,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // insert elements [self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption]; + + END_DATA_FETCHING } #pragma mark - Data Update @@ -187,6 +204,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { + BEGIN_DATA_FETCHING + __block int nodeTotalCnt = 0; NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count]; [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { @@ -222,6 +241,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; }); + + END_DATA_FETCHING } - (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -239,6 +260,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption { + BEGIN_DATA_FETCHING + // We need to keep data query on data source in the calling thread. NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; @@ -264,6 +287,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // reinsert the elements [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; }); + + END_DATA_FETCHING } - (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 { + BEGIN_DATA_FETCHING + // 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]; @@ -357,6 +384,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + + END_DATA_FETCHING } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -373,6 +402,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { + BEGIN_DATA_FETCHING + // The reloading operation required reloading the data // Loading data in the calling thread NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; @@ -388,6 +419,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; }); + + END_DATA_FETCHING } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -407,6 +440,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { + BEGIN_DATA_FETCHING + // Fetching data in calling thread NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; @@ -446,6 +481,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; }); + + END_DATA_FETCHING } #pragma mark - Data Querying From 6a2c4729100855bc99322082375319db075f2931 Mon Sep 17 00:00:00 2001 From: Li Tan Date: Mon, 9 Feb 2015 17:32:25 -0800 Subject: [PATCH 2/3] Add asyncDataFetchingEnabled as parameter of initializer of ASTableView & ASCollectionView Conflicts: AsyncDisplayKit/ASCollectionView.mm AsyncDisplayKit/Details/ASDataController.mm --- AsyncDisplayKit/ASCollectionView.h | 5 +++++ AsyncDisplayKit/ASCollectionView.mm | 10 +++++++++- AsyncDisplayKit/ASTableView.h | 5 +++++ AsyncDisplayKit/ASTableView.mm | 14 ++++++++++++- AsyncDisplayKit/Details/ASDataController.h | 5 +++++ AsyncDisplayKit/Details/ASDataController.mm | 22 ++++++++++++++------- 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index d65a900c60..51319b9821 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -34,6 +34,11 @@ */ @property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters; +/** + * Initializer. + */ +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ddd5ac254e..88d10c3a9f 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -101,6 +101,8 @@ static BOOL _isInterceptedSelector(SEL sel) BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; + + BOOL _asyncDataFetchingEnabled; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -113,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; @@ -126,13 +133,14 @@ 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)_proxyDelegate; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; _asyncDataSourceLocked = NO; _performingBatchUpdates = NO; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index e163bd69d1..6d4b111d32 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -34,6 +34,11 @@ */ @property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters; +/** + * initializer. + */ +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 2c2c43cf80..758782f725 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -110,6 +110,8 @@ static BOOL _isInterceptedSelector(SEL sel) ASFlowLayoutController *_layoutController; ASRangeController *_rangeController; + + BOOL _asyncDataFetchingEnabled; } @property (atomic, assign) BOOL asyncDataSouceLocked; @@ -123,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; @@ -132,13 +140,14 @@ 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)_proxyDelegate; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; _asyncDataSouceLocked = NO; return self; @@ -168,6 +177,9 @@ static BOOL _isInterceptedSelector(SEL sel) _proxyDataSource = nil; super.dataSource = nil; } else { + ASDisplayNodeAssert(!_asyncDataFetchingEnabled || ([asyncDataSource respondsToSelector:@selector(tableViewLockDataSourceForDataUpdating:)] && + [asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSourceForDataUpdating:)]), + @"The asyncDataSource need to implements \"tableViewLockDataSourceForDataUpdating\" and \"tableViewUnlockDataSourceForDataUpdating\" to handle data fetching in async mode."); _asyncDataSource = asyncDataSource; _proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; super.dataSource = (id)_proxyDataSource; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index f493eee28d..5825982fbc 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -119,6 +119,11 @@ typedef NSUInteger ASDataControllerAnimationOptions; */ @property (nonatomic, weak) id delegate; +/** + Designated iniailizer. + */ +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** @name Initial loading */ - (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 78356e94c5..6e400d7c6e 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -12,11 +12,18 @@ #ifdef ENABLE_ASYNC_DATA_FETCHING #define BEGIN_DATA_FETCHING \ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ \ - [_dataSource dataControllerLockDataSourceForDataUpdating]; + dispatch_block_t block = ^{ #define END_DATA_FETCHING \ - [_dataSource dataControllerUnlockDataSourceForDataUpdating]; \ - }); + }; \ + if (_asyncDataFetchingEnabled) { \ + [_dataSource dataControllerLockDataSourceForDataUpdating]; \ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ \ + block(); \ + [_dataSource dataControllerUnlockDataSourceForDataUpdating]; \ + }); \ + } else { \ + block(); \ + } #else #define BEGIN_DATA_FETCHING #define END_DATA_FETCHING @@ -73,6 +80,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { NSMutableArray *_nodes; NSMutableArray *_pendingBlocks; + BOOL _asyncDataFetchingEnabled; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -81,11 +89,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; @@ -143,8 +152,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Initial Data Loading -- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption -{ +- (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { BEGIN_DATA_FETCHING NSMutableArray *indexPaths = [NSMutableArray array]; From 831aa47ae05768eec883f6b9be54e8b5fcdb3b91 Mon Sep 17 00:00:00 2001 From: Li Tan Date: Wed, 11 Feb 2015 19:15:08 -0800 Subject: [PATCH 3/3] Address comments --- AsyncDisplayKit/ASCollectionView.h | 22 +- AsyncDisplayKit/ASCollectionView.mm | 12 +- AsyncDisplayKit/ASTableView.h | 18 +- AsyncDisplayKit/ASTableView.mm | 27 +- AsyncDisplayKit/Details/ASDataController.h | 28 +- AsyncDisplayKit/Details/ASDataController.mm | 297 ++++++++---------- .../ASCollectionView/Sample/ViewController.m | 11 +- examples/Kittens/Sample/ViewController.m | 22 +- 8 files changed, 226 insertions(+), 211 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 51319b9821..bc2c1226a6 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -36,6 +36,14 @@ /** * 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; @@ -112,25 +120,23 @@ */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; -@optional - /** - * Indicator to lock the data source for data loading in asyn mode. + * 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. + * @param collectionView The sender. */ -- (void)collectionViewLockDataSourceForDataUpdating:(ASCollectionView *)collectionView; +- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView; /** - * Indicator to unlock the data source for data loading in asyn mode. + * 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. + * @param collectionView The sender. */ -- (void)collectionViewUnlockDataSourceForDataUpdating:(ASCollectionView *)collectionView; +- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView; @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 88d10c3a9f..fff4cc2f65 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -387,23 +387,23 @@ static BOOL _isInterceptedSelector(SEL sel) } } -- (void)dataControllerLockDataSourceForDataUpdating +- (void)dataControllerLockDataSource { ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); self.asyncDataSourceLocked = YES; - if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSourceForDataUpdating:)]) { - [_asyncDataSource collectionViewLockDataSourceForDataUpdating:self]; + if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { + [_asyncDataSource collectionViewLockDataSource:self]; } } -- (void)dataControllerUnlockDataSourceForDataUpdating +- (void)dataControllerUnlockDataSource { ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has alredy been unlocked !"); self.asyncDataSourceLocked = NO; - if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSourceForDataUpdating:)]) { - [_asyncDataSource collectionViewUnlockDataSourceForDataUpdating:self]; + if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { + [_asyncDataSource collectionViewUnlockDataSource:self]; } } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 6d4b111d32..0c494e28ed 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -36,6 +36,14 @@ /** * 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; @@ -114,25 +122,23 @@ */ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; -@optional - /** - * Indicator to lock the data source for data loading in asyn mode. + * 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)tableViewLockDataSourceForDataUpdating:(ASTableView *)tableView; +- (void)tableViewLockDataSource:(ASTableView *)tableView; /** - * Indicator to unlock the data source for data loading in asyn mode. + * 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)tableViewUnlockDataSourceForDataUpdating:(ASTableView *)tableView; +- (void)tableViewUnlockDataSource:(ASTableView *)tableView; @end diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 758782f725..8777896cad 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -114,7 +114,7 @@ static BOOL _isInterceptedSelector(SEL sel) BOOL _asyncDataFetchingEnabled; } -@property (atomic, assign) BOOL asyncDataSouceLocked; +@property (atomic, assign) BOOL asyncDataSourceLocked; @end @@ -148,7 +148,7 @@ static BOOL _isInterceptedSelector(SEL sel) super.delegate = (id)_proxyDelegate; _asyncDataFetchingEnabled = asyncDataFetchingEnabled; - _asyncDataSouceLocked = NO; + _asyncDataSourceLocked = NO; return self; } @@ -177,9 +177,6 @@ static BOOL _isInterceptedSelector(SEL sel) _proxyDataSource = nil; super.dataSource = nil; } else { - ASDisplayNodeAssert(!_asyncDataFetchingEnabled || ([asyncDataSource respondsToSelector:@selector(tableViewLockDataSourceForDataUpdating:)] && - [asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSourceForDataUpdating:)]), - @"The asyncDataSource need to implements \"tableViewLockDataSourceForDataUpdating\" and \"tableViewUnlockDataSourceForDataUpdating\" to handle data fetching in async mode."); _asyncDataSource = asyncDataSource; _proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; super.dataSource = (id)_proxyDataSource; @@ -441,25 +438,25 @@ static BOOL _isInterceptedSelector(SEL sel) return CGSizeMake(self.bounds.size.width, FLT_MAX); } -- (void)dataControllerLockDataSourceForDataUpdating +- (void)dataControllerLockDataSource { - ASDisplayNodeAssert(!self.asyncDataSouceLocked, @"The data source has already been locked !"); + ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked !"); - self.asyncDataSouceLocked = YES; + self.asyncDataSourceLocked = YES; - if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSourceForDataUpdating:)]) { - [_asyncDataSource tableViewLockDataSourceForDataUpdating:self]; + if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) { + [_asyncDataSource tableViewLockDataSource:self]; } } -- (void)dataControllerUnlockDataSourceForDataUpdating +- (void)dataControllerUnlockDataSource { - ASDisplayNodeAssert(self.asyncDataSouceLocked, @"The data source has already been unlocked !"); + ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked !"); - self.asyncDataSouceLocked = NO; + self.asyncDataSourceLocked = NO; - if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSourceForDataUpdating:)]) { - [_asyncDataSource tableViewUnlockDataSourceForDataUpdating:self]; + if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) { + [_asyncDataSource tableViewUnlockDataSource:self]; } } diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 5825982fbc..4be1ea54b0 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -3,19 +3,6 @@ #import #import -/** - 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 ASDataController; @@ -50,12 +37,12 @@ typedef NSUInteger ASDataControllerAnimationOptions; /** Lock the data source for data fetching. */ -- (void)dataControllerLockDataSourceForDataUpdating; +- (void)dataControllerLockDataSource; /** Unlock the data source after data fetching. */ -- (void)dataControllerUnlockDataSourceForDataUpdating; +- (void)dataControllerUnlockDataSource; @end @@ -120,7 +107,16 @@ typedef NSUInteger ASDataControllerAnimationOptions; @property (nonatomic, weak) id delegate; /** - Designated iniailizer. + * 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; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 6e400d7c6e..27e44aaec3 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -10,25 +10,6 @@ #import "ASMultidimensionalArrayUtils.h" #import "ASDisplayNodeInternal.h" -#ifdef ENABLE_ASYNC_DATA_FETCHING - #define BEGIN_DATA_FETCHING \ - dispatch_block_t block = ^{ - #define END_DATA_FETCHING \ - }; \ - if (_asyncDataFetchingEnabled) { \ - [_dataSource dataControllerLockDataSourceForDataUpdating]; \ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ \ - block(); \ - [_dataSource dataControllerUnlockDataSourceForDataUpdating]; \ - }); \ - } else { \ - block(); \ - } -#else - #define BEGIN_DATA_FETCHING - #define END_DATA_FETCHING -#endif - #define INSERT_NODES(multidimensionalArray, indexPath, elements, animationOption) \ { \ if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:withAnimationOption:)]) { \ @@ -150,31 +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 { - BEGIN_DATA_FETCHING + [self performDataFetchingWithBlock:^{ + NSMutableArray *indexPaths = [NSMutableArray array]; + NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; - NSMutableArray *indexPaths = [NSMutableArray array]; + // insert sections + [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0]; - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0]; - - 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]; - END_DATA_FETCHING + }]; } #pragma mark - Data Update @@ -212,45 +203,43 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - BEGIN_DATA_FETCHING - - __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]; - END_DATA_FETCHING + __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 @@ -268,35 +257,33 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - BEGIN_DATA_FETCHING + [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]; - // 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); + }]; - END_DATA_FETCHING + // reinsert the elements + [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; + }); + }]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -382,18 +369,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - BEGIN_DATA_FETCHING + [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]]]; + } - // 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]; - - END_DATA_FETCHING + [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + }]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -410,25 +395,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - BEGIN_DATA_FETCHING - - // 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); + }]; - END_DATA_FETCHING + [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + }); + }]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -448,49 +431,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { - BEGIN_DATA_FETCHING + [self performDataFetchingWithBlock:^{ + // Fetching data in calling thread + NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; - // 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); - END_DATA_FETCHING + + // 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 diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 3400e2a286..895e817021 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -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 diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index b0a865deab..0de62f2823 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -12,6 +12,7 @@ #import "ViewController.h" #import +#import #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