Merge pull request #1525 from maicki/ASCollectionViewASTableViewRespondsToSelector

Add caching respondsToSelector calls in ASCollectionView and ASTableView
This commit is contained in:
appleguy 2016-04-19 19:45:19 -07:00
commit dbc68c3a8f
2 changed files with 108 additions and 50 deletions

View File

@ -106,9 +106,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
NSMutableArray *_batchUpdateBlocks; NSMutableArray *_batchUpdateBlocks;
BOOL _asyncDataFetchingEnabled; BOOL _asyncDataFetchingEnabled;
BOOL _asyncDelegateImplementsScrollviewDidScroll;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
BOOL _isDeallocating; BOOL _isDeallocating;
@ -133,6 +130,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
* The collection view never queried your data source before the update to see that it actually had 0 items. * The collection view never queried your data source before the update to see that it actually had 0 items.
*/ */
BOOL _superIsPendingDataLoad; BOOL _superIsPendingDataLoad;
struct {
unsigned int asyncDelegateScrollViewDidScroll:1;
unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1;
unsigned int asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath:1;
unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath:1;
unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated:1;
unsigned int asyncDelegateCollectionViewWillBeginBatchFetchWithContext:1;
unsigned int asyncDelegateShouldBatchFetchForCollectionView:1;
} _asyncDelegateFlags;
struct {
unsigned int asyncDataSourceConstrainedSizeForNode:1;
unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
unsigned int asyncDataSourceCollectionViewLockDataSource:1;
unsigned int asyncDataSourceCollectionViewUnlockDataSource:1;
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
} _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@ -333,16 +350,22 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
_asyncDataSourceImplementsNodeBlockForItemAtIndexPath = NO; memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
_asyncDataSourceImplementsConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceImplementsNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];;
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceImplementsNodeBlockForItemAtIndexPath || [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]); ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
} }
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource; super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
@ -363,11 +386,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = NO;
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)] ? 1 : 0);
_asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
_asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)];
} }
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate; super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
@ -566,7 +597,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
cellNode.scrollView = collectionView; cellNode.scrollView = collectionView;
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
} }
@ -586,7 +617,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath) {
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath];
} }
@ -597,7 +628,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated) {
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
inScrollView:scrollView inScrollView:scrollView
withCellFrame:collectionCell.frame]; withCellFrame:collectionCell.frame];
} }
if (_asyncDelegateImplementsScrollviewDidScroll) { if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
[_asyncDelegate scrollViewDidScroll:scrollView]; [_asyncDelegate scrollViewDidScroll:scrollView];
} }
} }
@ -637,7 +668,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
} }
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
} }
} }
@ -747,8 +778,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)canBatchFetch - (BOOL)canBatchFetch
{ {
// if the delegate does not respond to this method, there is no point in starting to fetch // if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; BOOL canFetch = _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext;
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) { if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView) {
return [_asyncDelegate shouldBatchFetchForCollectionView:self]; return [_asyncDelegate shouldBatchFetchForCollectionView:self];
} else { } else {
return canFetch; return canFetch;
@ -788,7 +819,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)_beginBatchFetching - (void)_beginBatchFetching
{ {
[_batchContext beginBatchFetching]; [_batchContext beginBatchFetching];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
}); });
@ -800,7 +831,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
{ {
if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) { if (!_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath) {
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
@ -842,7 +873,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
// TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't // TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't
// implement a custom inspector. // implement a custom inspector.
if (_asyncDataSourceImplementsConstrainedSizeForNode) { if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) {
constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
} else { } else {
CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize;
@ -863,7 +894,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} }
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView) {
return [_asyncDataSource numberOfSectionsInCollectionView:self]; return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else { } else {
return 1; return 1;
@ -875,7 +906,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
self.asyncDataSourceLocked = YES; self.asyncDataSourceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) {
[_asyncDataSource collectionViewLockDataSource:self]; [_asyncDataSource collectionViewLockDataSource:self];
} }
} }
@ -885,7 +916,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
self.asyncDataSourceLocked = NO; self.asyncDataSourceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) {
[_asyncDataSource collectionViewUnlockDataSource:self]; [_asyncDataSource collectionViewUnlockDataSource:self];
} }
} }
@ -1002,13 +1033,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super insertItemsAtIndexPaths:indexPaths]; [super insertItemsAtIndexPaths:indexPaths];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super insertItemsAtIndexPaths:indexPaths]; [super insertItemsAtIndexPaths:indexPaths];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
@ -1023,13 +1053,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super deleteItemsAtIndexPaths:indexPaths]; [super deleteItemsAtIndexPaths:indexPaths];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super deleteItemsAtIndexPaths:indexPaths]; [super deleteItemsAtIndexPaths:indexPaths];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
@ -1044,13 +1073,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super insertSections:indexSet]; [super insertSections:indexSet];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super insertSections:indexSet]; [super insertSections:indexSet];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
@ -1065,13 +1093,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super deleteSections:indexSet]; [super deleteSections:indexSet];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super deleteSections:indexSet]; [super deleteSections:indexSet];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];

View File

@ -112,9 +112,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL _ignoreNodesConstrainedWidthChange; BOOL _ignoreNodesConstrainedWidthChange;
BOOL _queuedNodeHeightUpdate; BOOL _queuedNodeHeightUpdate;
BOOL _isDeallocating; BOOL _isDeallocating;
BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath;
BOOL _asyncDelegateImplementsScrollviewDidScroll;
NSMutableSet *_cellsForVisibilityUpdates; NSMutableSet *_cellsForVisibilityUpdates;
struct {
unsigned int asyncDelegateScrollViewDidScroll:1;
unsigned int asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath:1;
unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath:1;
unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated:1;
unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1;
unsigned int asyncDelegateTableViewWillBeginBatchFetchWithContext:1;
unsigned int asyncDelegateShouldBatchFetchForTableView:1;
} _asyncDelegateFlags;
struct {
unsigned int asyncDataSourceNumberOfSectionsInTableView:1;
unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewLockDataSource:1;
unsigned int asyncDataSourceTableViewUnlockDataSource:1;
} _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@ -260,13 +276,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
_dataSourceImplementsNodeBlockForRowAtIndexPath = NO;
memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_dataSourceImplementsNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
ASDisplayNodeAssertTrue(_dataSourceImplementsNodeBlockForRowAtIndexPath || [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]);
_proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)];
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath);
} }
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource; super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
@ -287,11 +310,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = NO;
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_asyncDelegateImplementsScrollviewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
_asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
_asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)];
} }
super.delegate = (id<UITableViewDelegate>)_proxyDelegate; super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
@ -584,7 +615,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
cellNode.scrollView = tableView; cellNode.scrollView = tableView;
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath) {
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
} }
@ -609,7 +640,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath) {
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath];
} }
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated) {
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
@ -642,7 +673,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
inScrollView:scrollView inScrollView:scrollView
withCellFrame:tableCell.frame]; withCellFrame:tableCell.frame];
} }
if (_asyncDelegateImplementsScrollviewDidScroll) { if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
[_asyncDelegate scrollViewDidScroll:scrollView]; [_asyncDelegate scrollViewDidScroll:scrollView];
} }
} }
@ -659,7 +690,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
} }
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
} }
} }
@ -722,8 +753,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (BOOL)canBatchFetch - (BOOL)canBatchFetch
{ {
// if the delegate does not respond to this method, there is no point in starting to fetch // if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; BOOL canFetch = _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext;
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]) { if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView) {
return [_asyncDelegate shouldBatchFetchForTableView:self]; return [_asyncDelegate shouldBatchFetchForTableView:self];
} else { } else {
return canFetch; return canFetch;
@ -763,7 +794,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)_beginBatchFetching - (void)_beginBatchFetching
{ {
[_batchContext beginBatchFetching]; [_batchContext beginBatchFetching];
if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext];
}); });
@ -1013,7 +1044,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
self.asyncDataSourceLocked = YES; self.asyncDataSourceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) {
[_asyncDataSource tableViewLockDataSource:self]; [_asyncDataSource tableViewLockDataSource:self];
} }
} }
@ -1024,7 +1055,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
self.asyncDataSourceLocked = NO; self.asyncDataSourceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) {
[_asyncDataSource tableViewUnlockDataSource:self]; [_asyncDataSource tableViewUnlockDataSource:self];
} }
} }
@ -1036,7 +1067,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
{ {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView) {
return [_asyncDataSource numberOfSectionsInTableView:self]; return [_asyncDataSource numberOfSectionsInTableView:self];
} else { } else {
return 1; // default section number return 1; // default section number