diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 6598dc9b2b..90f937ae5a 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -35,6 +35,19 @@ */ @property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters; +/** + * Initializer. + * + * @discussion If asyncDataFetching is enabled, the `AScollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and + * `collectionView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically + * from calling thread. + * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for + * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, + * we will lock the data source through `collectionViewLockDataSource`, and unlock it by `collectionViewUnlockDataSource` after the data fetching. + * The application should not update the data source while the data source is locked, to keep data consistence. + */ +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * @@ -108,6 +121,24 @@ */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; +/** + * Indicator to lock the data source for data fetching in asyn mode. + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * due to the data access in async mode. + * + * @param collectionView The sender. + */ +- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView; + +/** + * Indicator to unlock the data source for data fetching in asyn mode. + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * due to the data access in async mode. + * + * @param collectionView The sender. + */ +- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView; + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 36cc0e2f60..fff4cc2f65 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -101,8 +101,12 @@ static BOOL _isInterceptedSelector(SEL sel) BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; + + BOOL _asyncDataFetchingEnabled; } +@property (atomic, assign) BOOL asyncDataSourceLocked; + @end @implementation ASCollectionView @@ -111,6 +115,11 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark Lifecycle. - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout +{ + return [self initWithFrame:frame collectionViewLayout:layout asyncDataFetching:NO]; +} + +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; @@ -124,13 +133,16 @@ static BOOL _isInterceptedSelector(SEL sel) _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASDataController alloc] init]; + _dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; _dataController.delegate = _rangeController; _dataController.dataSource = self; _proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; super.delegate = (id)_proxyDelegate; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + _asyncDataSourceLocked = NO; + _performingBatchUpdates = NO; _batchUpdateBlocks = [NSMutableArray array]; @@ -375,6 +387,26 @@ static BOOL _isInterceptedSelector(SEL sel) } } +- (void)dataControllerLockDataSource +{ + ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); + + self.asyncDataSourceLocked = YES; + if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { + [_asyncDataSource collectionViewLockDataSource:self]; + } +} + +- (void)dataControllerUnlockDataSource +{ + ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has alredy been unlocked !"); + + self.asyncDataSourceLocked = NO; + if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { + [_asyncDataSource collectionViewUnlockDataSource:self]; + } +} + #pragma mark - #pragma mark ASRangeControllerDelegate. diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 30f974b80f..c5a4c7641f 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -35,6 +35,19 @@ */ @property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters; +/** + * initializer. + * + * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and + * `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically + * from calling thread. + * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for + * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, + * we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching. + * The application should not update the data source while the data source is locked, to keep data consistence. + */ +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * @@ -110,6 +123,24 @@ */ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; +/** + * Indicator to lock the data source for data fetching in asyn mode. + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * due to the data access in async mode. + * + * @param tableView The sender. + */ +- (void)tableViewLockDataSource:(ASTableView *)tableView; + +/** + * Indicator to unlock the data source for data fetching in asyn mode. + * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception + * due to the data access in async mode. + * + * @param tableView The sender. + */ +- (void)tableViewUnlockDataSource:(ASTableView *)tableView; + @end diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 4169dc5f8a..8777896cad 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -110,8 +110,12 @@ static BOOL _isInterceptedSelector(SEL sel) ASFlowLayoutController *_layoutController; ASRangeController *_rangeController; + + BOOL _asyncDataFetchingEnabled; } +@property (atomic, assign) BOOL asyncDataSourceLocked; + @end @implementation ASTableView @@ -121,6 +125,12 @@ static BOOL _isInterceptedSelector(SEL sel) - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { + return [self initWithFrame:frame style:style asyncDataFetching:NO]; +} + +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + if (!(self = [super initWithFrame:frame style:style])) return nil; @@ -130,13 +140,16 @@ static BOOL _isInterceptedSelector(SEL sel) _rangeController.layoutController = _layoutController; _rangeController.delegate = self; - _dataController = [[ASDataController alloc] init]; + _dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; _dataController.dataSource = self; _dataController.delegate = _rangeController; _proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; super.delegate = (id)_proxyDelegate; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + _asyncDataSourceLocked = NO; + return self; } @@ -425,6 +438,28 @@ static BOOL _isInterceptedSelector(SEL sel) return CGSizeMake(self.bounds.size.width, FLT_MAX); } +- (void)dataControllerLockDataSource +{ + ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked !"); + + self.asyncDataSourceLocked = YES; + + if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) { + [_asyncDataSource tableViewLockDataSource:self]; + } +} + +- (void)dataControllerUnlockDataSource +{ + ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked !"); + + self.asyncDataSourceLocked = NO; + + if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) { + [_asyncDataSource tableViewUnlockDataSource:self]; + } +} + - (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section { return [_asyncDataSource tableView:self numberOfRowsInSection:section]; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 3d8d85fd9f..cc58603329 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -28,13 +28,23 @@ typedef NSUInteger ASDataControllerAnimationOptions; /** Fetch the number of rows in specific section. */ -- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section; +- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section; /** Fetch the number of sections. */ - (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController; +/** + Lock the data source for data fetching. + */ +- (void)dataControllerLockDataSource; + +/** + Unlock the data source after data fetching. + */ +- (void)dataControllerUnlockDataSource; + @end /** @@ -97,6 +107,20 @@ typedef NSUInteger ASDataControllerAnimationOptions; */ @property (nonatomic, weak) id delegate; +/** + * Designated iniailizer. + * + * @param asyncDataFetchingEnabled Enable the data fetching in async mode. + + * @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread. + * Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread + * while allocating cell on main thread, which is frequently reported issue for handing large scale data. On another hand, the application code + * will take the responsibility to avoid data inconsistence. Specifically, we will lock the data source through `dataControllerLockDataSource`, + * and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while + * the data source is locked. + */ +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; + /** @name Initial loading */ - (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 483b5d29c2..27e44aaec3 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -61,6 +61,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { NSMutableArray *_nodes; NSMutableArray *_pendingBlocks; + BOOL _asyncDataFetchingEnabled; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -69,11 +70,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @implementation ASDataController -- (instancetype)init { +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled { if (self = [super init]) { _nodes = [NSMutableArray array]; _pendingBlocks = [NSMutableArray array]; _batchUpdateCounter = 0; + _asyncDataFetchingEnabled = asyncDataFetchingEnabled; } return self; @@ -129,27 +131,41 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }); } +- (void)performDataFetchingWithBlock:(dispatch_block_t)block { + if (_asyncDataFetchingEnabled) { + [_dataSource dataControllerLockDataSource]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + block(); + [_dataSource dataControllerUnlockDataSource]; + }); + } else { + block(); + } +} + #pragma mark - Initial Data Loading - (void)initialDataLoadingWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { - NSMutableArray *indexPaths = [NSMutableArray array]; + [self performDataFetchingWithBlock:^{ + NSMutableArray *indexPaths = [NSMutableArray array]; + NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + // insert sections + [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0]; - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOption:0]; + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; + } } - } - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption]; + // insert elements + [self insertRowsAtIndexPaths:indexPaths withAnimationOption:animationOption]; + + }]; } #pragma mark - Data Update @@ -187,41 +203,43 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - __block int nodeTotalCnt = 0; - NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger cnt = [_dataSource dataController:self rowsInSection:idx]; - [nodeCounts addObject:@(cnt)]; - nodeTotalCnt += cnt; - }]; - - NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; - - __block NSUInteger idx = 0; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) { - NSUInteger cnt = [nodeCounts[idx++] unsignedIntegerValue]; - - for (int i = 0; i < cnt; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIdx]; - [indexPaths addObject:indexPath]; - - ASCellNode *node = [_dataSource dataController:self nodeAtIndexPath:indexPath]; - [nodes addObject:node]; - } - }]; - - dispatch_async([[self class] sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count]; - for (NSUInteger i = 0; i < indexSet.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - INSERT_SECTIONS(_nodes , indexSet, sectionArray, animationOption); + [self performDataFetchingWithBlock:^{ + __block int nodeTotalCnt = 0; + NSMutableArray *nodeCounts = [NSMutableArray arrayWithCapacity:indexSet.count]; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSUInteger cnt = [_dataSource dataController:self rowsInSection:idx]; + [nodeCounts addObject:@(cnt)]; + nodeTotalCnt += cnt; }]; - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; - }); + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; + NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeTotalCnt]; + + __block NSUInteger idx = 0; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) { + NSUInteger cnt = [nodeCounts[idx++] unsignedIntegerValue]; + + for (int i = 0; i < cnt; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIdx]; + [indexPaths addObject:indexPath]; + + ASCellNode *node = [_dataSource dataController:self nodeAtIndexPath:indexPath]; + [nodes addObject:node]; + } + }]; + + dispatch_async([[self class] sizingQueue], ^{ + [self syncUpdateDataWithBlock:^{ + NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count]; + for (NSUInteger i = 0; i < indexSet.count; i++) { + [sectionArray addObject:[NSMutableArray array]]; + } + INSERT_SECTIONS(_nodes , indexSet, sectionArray, animationOption); + }]; + + [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + }); + }]; } - (void)deleteSections:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -239,31 +257,33 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadSections:(NSIndexSet *)sections withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - // We need to keep data query on data source in the calling thread. - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; - NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; + [self performDataFetchingWithBlock:^{ + // We need to keep data query on data source in the calling thread. + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; + NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [updatedIndexPaths addObject:indexPath]; - [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } - }]; - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - // remove elements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections); - DELETE_NODES(_nodes, indexPaths, animationOption); + NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; + for (NSUInteger i = 0; i < rowNum; i++) { + NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + [updatedIndexPaths addObject:indexPath]; + [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } }]; - // reinsert the elements - [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; - }); + dispatch_async([ASDataController sizingQueue], ^{ + [self syncUpdateDataWithBlock:^{ + // remove elements + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections); + DELETE_NODES(_nodes, indexPaths, animationOption); + }]; + + // reinsert the elements + [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; + }); + }]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -349,14 +369,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - // sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; - } + [self performDataFetchingWithBlock:^{ + // sort indexPath to avoid messing up the index when inserting in several batches + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + } - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + }]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -373,21 +395,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { - // The reloading operation required reloading the data - // Loading data in the calling thread - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - }]; - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - DELETE_NODES(_nodes, indexPaths, animationOption); + [self performDataFetchingWithBlock:^{ + // The reloading operation required reloading the data + // Loading data in the calling thread + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; }]; - [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; - }); + dispatch_async([ASDataController sizingQueue], ^{ + [self syncUpdateDataWithBlock:^{ + DELETE_NODES(_nodes, indexPaths, animationOption); + }]; + + [self _batchInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOption]; + }); + }]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOption:(ASDataControllerAnimationOptions)animationOption @@ -407,45 +431,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadDataWithAnimationOption:(ASDataControllerAnimationOptions)animationOption { - // Fetching data in calling thread - NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; + [self performDataFetchingWithBlock:^{ + // Fetching data in calling thread + NSMutableArray *updatedNodes = [[NSMutableArray alloc] init]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init]; - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; + NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; - [updatedIndexPaths addObject:indexPath]; - [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } - } - - dispatch_async([ASDataController sizingQueue], ^{ - [self syncUpdateDataWithBlock:^{ - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes); - DELETE_NODES(_nodes, indexPaths, animationOption); - - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)]; - DELETE_SECTIONS(_nodes, indexSet, animationOption); - - - // Insert section - - NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum]; - for (int i = 0; i < sectionNum; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; + [updatedIndexPaths addObject:indexPath]; + [updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; } + } - INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections, animationOption); + dispatch_async([ASDataController sizingQueue], ^{ + [self syncUpdateDataWithBlock:^{ - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes); + DELETE_NODES(_nodes, indexPaths, animationOption); - [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; - }); + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)]; + DELETE_SECTIONS(_nodes, indexSet, animationOption); + + + // Insert section + + NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum]; + for (int i = 0; i < sectionNum; i++) { + [sections addObject:[[NSMutableArray alloc] init]]; + } + + INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections, animationOption); + + }]; + + [self _batchInsertNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOption]; + }); + }]; } #pragma mark - Data Querying 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