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