diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8c2c6cffdc..ea9347401a 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -368,6 +368,7 @@ CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */; }; CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; + CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -798,6 +799,7 @@ CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipsWindow.h; sourceTree = ""; }; CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTipsWindow.m; sourceTree = ""; }; CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = ""; }; + CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeManagingNode.h; sourceTree = ""; }; CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIGListAdapterBasedDataSource.m; sourceTree = ""; }; CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = ""; }; CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = ""; }; @@ -1002,6 +1004,7 @@ 25E327551C16819500A2170C /* ASPagerNode.m */, A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */, A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */, + CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */, ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */, D785F6601A74327E00291744 /* ASScrollNode.h */, D785F6611A74327E00291744 /* ASScrollNode.mm */, @@ -1533,6 +1536,7 @@ 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */, CCA282B81E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h in Headers */, B35062571B010F070018CF92 /* ASAssert.h in Headers */, + CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */, B35062581B010F070018CF92 /* ASAvailability.h in Headers */, DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */, CC0F88621E4281E200576FED /* ASSectionController.h in Headers */, diff --git a/CHANGELOG.md b/CHANGELOG.md index b7ddb7aaa3..28c8c16702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix `__has_include` check in ASLog.h [Philipp Smorygo](Philipp.Smorygo@jetbrains.com) - Fix potential deadlock in ASControlNode [Garrett Moon](https://github.com/garrettmoon) - [Yoga Beta] Improvements to the experimental support for Yoga layout [Scott Goodson](appleguy) +- Make cell node `indexPath` and `supplementaryElementKind` atomic so you can read from any thread. (Adlai-Holler)[https://github.com/Adlai-Holler] (#49)[https://github.com/TextureGroup/Texture/pull/74] - Update the rasterization API and un-deprecate it. [Adlai Holler](https://github.com/Adlai-Holler)[#82](https://github.com/TextureGroup/Texture/pull/49) - Simplified & optimized hashing code. [Adlai Holler](https://github.com/Adlai-Holler) [#86](https://github.com/TextureGroup/Texture/pull/86) - Improve the performance & safety of ASDisplayNode subnodes. [Adlai Holler](https://github.com/Adlai-Holler) [#223](https://github.com/TextureGroup/Texture/pull/223) diff --git a/Source/ASCellNode.h b/Source/ASCellNode.h index a2017fa00e..715d45d132 100644 --- a/Source/ASCellNode.h +++ b/Source/ASCellNode.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASCellNode, ASTextNode; +@protocol ASRangeManagingNode; typedef NSUInteger ASCellNodeAnimation; @@ -87,7 +88,7 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { * @return The supplementary element kind, or @c nil if this node does not represent a supplementary element. */ //TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind -@property (nonatomic, copy, readonly, nullable) NSString *supplementaryElementKind; +@property (atomic, copy, readonly, nullable) NSString *supplementaryElementKind; /* * The layout attributes currently assigned to this node, if any. @@ -113,10 +114,8 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { /** * The current index path of this cell node, or @c nil if this node is * not a valid item inside a table node or collection node. - * - * @note This property must be accessed on the main thread. */ -@property (nonatomic, readonly, nullable) NSIndexPath *indexPath; +@property (atomic, readonly, nullable) NSIndexPath *indexPath; /** * The backing view controller, or @c nil if the node wasn't initialized with backing view controller @@ -126,10 +125,9 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { /** - * The owning node (ASCollectionNode/ASTableNode) of this cell node, or @c nil if this node is - * not a valid item inside a table node or collection node or if those nodes are nil. + * The table- or collection-node that this cell is a member of, if any. */ -@property (weak, nonatomic, readonly, nullable) ASDisplayNode *owningNode; +@property (atomic, weak, readonly, nullable) id owningNode; /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index c0ad6cea50..1dde6b06b2 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -44,12 +44,6 @@ ASDisplayNode *_viewControllerNode; UIViewController *_viewController; BOOL _suspendInteractionDelegate; - - struct { - unsigned int isTableNode:1; - unsigned int isCollectionNode:1; - } _owningNodeType; - } @end @@ -165,19 +159,6 @@ } } -- (void)setOwningNode:(ASDisplayNode *)owningNode -{ - _owningNode = owningNode; - - memset(&_owningNodeType, 0, sizeof(_owningNodeType)); - - if ([owningNode isKindOfClass:[ASTableNode class]]) { - _owningNodeType.isTableNode = 1; - } else if ([owningNode isKindOfClass:[ASCollectionNode class]]) { - _owningNodeType.isCollectionNode = 1; - } -} - - (void)__setSelectedFromUIKit:(BOOL)selected; { if (selected != _selected) { @@ -198,15 +179,7 @@ - (NSIndexPath *)indexPath { - ASDisplayNodeAssertMainThread(); - - if (_owningNodeType.isTableNode) { - return [(ASTableNode *)self.owningNode indexPathForNode:self]; - } else if (_owningNodeType.isCollectionNode) { - return [(ASCollectionNode *)self.owningNode indexPathForNode:self]; - } - - return nil; + return [self.owningNode indexPathForNode:self]; } - (UIViewController *)viewController diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 7d1ff31e7d..b3b8e30623 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -20,6 +20,7 @@ #import #import #import +#import @protocol ASCollectionViewLayoutFacilitatorProtocol; @protocol ASCollectionDelegate; @@ -32,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN * ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used * as a subnode of another node, and provide room for many (great) features and improvements later on. */ -@interface ASCollectionNode : ASDisplayNode +@interface ASCollectionNode : ASDisplayNode - (instancetype)init NS_UNAVAILABLE; diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 24dbdc0c38..469d73b46b 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -155,7 +155,7 @@ __weak __typeof__(self) weakSelf = self; [self setViewBlock:^{ __typeof__(self) strongSelf = weakSelf; - return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)]; + return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; }]; } return self; diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 04d79a8a8e..fe7d0f5109 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -80,7 +80,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; @@ -250,10 +250,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { - return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil eventLog:nil]; + return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil owningNode:nil eventLog:nil]; } -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator eventLog:(ASEventLog *)eventLog +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator owningNode:(ASCollectionNode *)owningNode eventLog:(ASEventLog *)eventLog { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; @@ -273,9 +273,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASDataController alloc] initWithDataSource:self eventLog:eventLog]; + _dataController = [[ASDataController alloc] initWithDataSource:self node:owningNode eventLog:eventLog]; _dataController.delegate = _rangeController; - _dataController.environmentDelegate = self; _batchContext = [[ASBatchContext alloc] init]; @@ -1649,11 +1648,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; } -- (id)dataControllerEnvironment -{ - return self.collectionNode; -} - #pragma mark - ASDataControllerSource optional methods - (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath diff --git a/Source/ASRangeManagingNode.h b/Source/ASRangeManagingNode.h new file mode 100644 index 0000000000..2716efbe4f --- /dev/null +++ b/Source/ASRangeManagingNode.h @@ -0,0 +1,35 @@ +// +// ASRangeManagingNode.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@class ASCellNode; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Basically ASTableNode or ASCollectionNode. + */ +@protocol ASRangeManagingNode + +/** + * Retrieve the index path for the given node, if it's a member of this container. + * + * @param node The node. + * @return The index path, or nil if the node is not part of this container. + */ +- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)node; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index 1bddf68cd6..a68efc47cb 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -19,7 +19,7 @@ #import #import #import - +#import NS_ASSUME_NONNULL_BEGIN @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN * ASTableNode is a node based class that wraps an ASTableView. It can be used * as a subnode of another node, and provide room for many (great) features and improvements later on. */ -@interface ASTableNode : ASDisplayNode +@interface ASTableNode : ASDisplayNode - (instancetype)init; // UITableViewStylePlain - (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index cb9a6cba3f..a94441cc23 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -80,7 +80,7 @@ [self setViewBlock:^{ // Variable will be unused if event logging is off. __unused __typeof__(self) strongSelf = weakSelf; - return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil eventLog:ASDisplayNodeGetEventLog(strongSelf)]; + return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; }]; } return self; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 27411e95e3..c7cc544c9c 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -147,7 +147,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark ASTableView -@interface ASTableView () +@interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -286,8 +286,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark Lifecycle -- (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { + return [self _initWithFrame:frame style:style dataControllerClass:nil owningNode:nil eventLog:nil]; +} + +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog +{ + if (!(self = [super initWithFrame:frame style:style])) { + return nil; + } + _cellsForVisibilityUpdates = [NSMutableSet set]; + _cellsForLayoutUpdates = [NSMutableSet set]; + if (!dataControllerClass) { + dataControllerClass = [[self class] dataControllerClass]; + } + _layoutController = [[ASTableLayoutController alloc] initWithTableView:self]; _rangeController = [[ASRangeController alloc] init]; @@ -295,13 +309,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] initWithDataSource:self eventLog:eventLog]; + _dataController = [[dataControllerClass alloc] initWithDataSource:self node:tableNode eventLog:eventLog]; _dataController.delegate = _rangeController; - _dataController.environmentDelegate = self; - + _leadingScreensForBatching = 2.0; _batchContext = [[ASBatchContext alloc] init]; - + _automaticallyAdjustsContentOffset = NO; _nodesConstrainedWidth = self.bounds.size.width; @@ -313,25 +326,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; super.dataSource = (id)_proxyDataSource; [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; -} - -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style -{ - return [self _initWithFrame:frame style:style dataControllerClass:nil eventLog:nil]; -} - -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super initWithFrame:frame style:style])) { - return nil; - } - _cellsForVisibilityUpdates = [NSMutableSet set]; - _cellsForLayoutUpdates = [NSMutableSet set]; - if (!dataControllerClass) { - dataControllerClass = [[self class] dataControllerClass]; - } - - [self configureWithDataControllerClass:dataControllerClass eventLog:eventLog]; if (!AS_AT_LEAST_IOS9) { _retainedLayer = self.layer; @@ -1734,13 +1728,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return (fabs(rect.size.height - size.height) < FLT_EPSILON); } -#pragma mark - ASDataControllerEnvironmentDelegate - -- (id)dataControllerEnvironment -{ - return self.tableNode; -} - #pragma mark - _ASTableViewCellDelegate - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell diff --git a/Source/ASTableViewInternal.h b/Source/ASTableViewInternal.h index f6f7e41f86..93ef92a211 100644 --- a/Source/ASTableViewInternal.h +++ b/Source/ASTableViewInternal.h @@ -40,7 +40,7 @@ * * @param eventLog An event log passed through to the data controller. */ -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog; +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog; /// Set YES and we'll log every time we call [super insertRows…] etc @property (nonatomic) BOOL test_enableSuperUpdateCallLogging; diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 17aef3d7fc..2c58ef1648 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -43,6 +43,7 @@ #import #import #import +#import #import #import diff --git a/Source/Details/ASCollectionElement.h b/Source/Details/ASCollectionElement.h index 3aea125b96..5c31d28af8 100644 --- a/Source/Details/ASCollectionElement.h +++ b/Source/Details/ASCollectionElement.h @@ -19,6 +19,7 @@ #import @class ASDisplayNode; +@protocol ASRangeManagingNode; NS_ASSUME_NONNULL_BEGIN @@ -28,13 +29,13 @@ AS_SUBCLASSING_RESTRICTED //TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind @property (nonatomic, readonly, copy, nullable) NSString *supplementaryElementKind; @property (nonatomic, assign) ASSizeRange constrainedSize; -@property (nonatomic, weak) ASDisplayNode *owningNode; +@property (nonatomic, readonly, weak) id owningNode; @property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection; - (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock supplementaryElementKind:(nullable NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - owningNode:(ASDisplayNode *)owningNode + owningNode:(id)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection; /** diff --git a/Source/Details/ASCollectionElement.mm b/Source/Details/ASCollectionElement.mm index 8aa73df675..c7803e1d80 100644 --- a/Source/Details/ASCollectionElement.mm +++ b/Source/Details/ASCollectionElement.mm @@ -34,7 +34,7 @@ - (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock supplementaryElementKind:(NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - owningNode:(ASDisplayNode *)owningNode + owningNode:(id)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection { NSAssert(nodeBlock != nil, @"Node block must not be nil"); diff --git a/Source/Details/ASCollectionInternal.h b/Source/Details/ASCollectionInternal.h index 7282b8a025..e7e56a1c92 100644 --- a/Source/Details/ASCollectionInternal.h +++ b/Source/Details/ASCollectionInternal.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASRangeController; @interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator eventLog:(nullable ASEventLog *)eventLog; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator owningNode:(nullable ASCollectionNode *)owningNode eventLog:(nullable ASEventLog *)eventLog; @property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode; @property (nonatomic, strong, readonly) ASDataController *dataController; diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index e8360afcc4..34b280e9fe 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASElementMap; @class ASLayout; @class _ASHierarchyChangeSet; +@protocol ASRangeManagingNode; @protocol ASTraitEnvironment; @protocol ASSectionContext; @@ -96,12 +97,6 @@ extern NSString * const ASCollectionInvalidUpdateException; @end -@protocol ASDataControllerEnvironmentDelegate - -- (nullable id)dataControllerEnvironment; - -@end - /** Delegate for notify the data updating of data controller. These methods will be invoked from main thread right now, but it may be moved to background thread in the future. @@ -160,17 +155,30 @@ extern NSString * const ASCollectionInvalidUpdateException; */ @interface ASDataController : NSObject -- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/** + * The node that owns this data controller, if any. + * + * NOTE: Soon we will drop support for using ASTableView/ASCollectionView without the node, so this will be non-null. + */ +@property (nonatomic, nullable, weak, readonly) id node; /** * The map that is currently displayed. The "UIKit index space." + * + * This property will only be changed on the main thread. */ -@property (nonatomic, strong, readonly) ASElementMap *visibleMap; +@property (atomic, copy, readonly) ASElementMap *visibleMap; /** * The latest map fetched from the data source. May be more recent than @c visibleMap. + * + * This property will only be changed on the main thread. */ -@property (nonatomic, strong, readonly) ASElementMap *pendingMap; +@property (atomic, copy, readonly) ASElementMap *pendingMap; /** Data source for fetching data info. @@ -187,11 +195,6 @@ extern NSString * const ASCollectionInvalidUpdateException; */ @property (nonatomic, weak) id delegate; -/** - * - */ -@property (nonatomic, weak) id environmentDelegate; - /** * Delegate for preparing layouts. Main thead only. */ diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index d3c1170abd..6f3ee8d2a8 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -27,6 +27,7 @@ #import #import #import +#import #import #import #import @@ -85,18 +86,21 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } _dataSourceFlags; } +@property (atomic, copy, readwrite) ASElementMap *pendingMap; +@property (atomic, copy, readwrite) ASElementMap *visibleMap; @end @implementation ASDataController #pragma mark - Lifecycle -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog +- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(ASEventLog *)eventLog { if (!(self = [super init])) { return nil; } + _node = node; _dataSource = dataSource; _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; @@ -110,7 +114,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * _eventLog = eventLog; #endif - _visibleMap = _pendingMap = [[ASElementMap alloc] init]; + self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; _nextSectionID = 0; @@ -124,14 +128,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * return self; } -- (instancetype)init -{ - ASDisplayNodeFailAssert(@"Failed to call designated initializer."); - id fakeDataSource = nil; - ASEventLog *eventLog = nil; - return [self initWithDataSource:fakeDataSource eventLog:eventLog]; -} - + (NSUInteger)parallelProcessorCount { static NSUInteger parallelProcessorCount; @@ -277,9 +273,10 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } }]; } else if (_dataSourceFlags.supplementaryNodesOfKindInSection) { + id dataSource = _dataSource; [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = [_dataSource dataController:self supplementaryNodesOfKind:kind inSection:sectionIndex]; + NSUInteger itemCount = [dataSource dataController:self supplementaryNodesOfKind:kind inSection:sectionIndex]; for (NSUInteger i = 0; i < itemCount; i++) { [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; } @@ -296,7 +293,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * * @param map The element map into which to apply the change. * @param indexPaths The index paths belongs to sections whose supplementary nodes need to be repopulated. * @param changeSet The changeset that triggered this repopulation. - * @param owningNode The node that owns the new elements. * @param traitCollection The trait collection needed to initialize elements * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source @@ -304,7 +300,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map forSectionsContainingIndexPaths:(NSArray *)indexPaths changeSet:(_ASHierarchyChangeSet *)changeSet - owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection indexPathsAreNew:(BOOL)indexPathsAreNew shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges @@ -330,7 +325,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } for (NSString *kind in [self supplementaryKindsInSections:newSections]) { - [self _insertElementsIntoMap:map kind:kind forSections:newSections owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map kind:kind forSections:newSections traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -346,7 +341,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind forSections:(NSIndexSet *)sections - owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { @@ -357,7 +351,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } /** @@ -373,7 +367,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths - owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { @@ -390,12 +383,14 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); + id dataSource = self.dataSource; + id node = self.node; for (NSIndexPath *indexPath in indexPaths) { ASCellNodeBlock nodeBlock; if (isRowKind) { - nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath]; } else { - nodeBlock = [_dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + nodeBlock = [dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; } ASSizeRange constrainedSize; @@ -406,7 +401,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeBlock:nodeBlock supplementaryElementKind:isRowKind ? nil : kind constrainedSize:constrainedSize - owningNode:owningNode + owningNode:node traitCollection:traitCollection]; [map insertElement:element atIndexPath:indexPath]; } @@ -544,14 +539,12 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * // Step 1: Update the mutable copies to match the data source's state [self _updateSectionContextsInMap:mutableMap changeSet:changeSet]; - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - __weak ASDisplayNode *owningNode = (ASDisplayNode *)environment; // This is gross! - ASPrimitiveTraitCollection existingTraitCollection = [environment primitiveTraitCollection]; - [self _updateElementsInMap:mutableMap changeSet:changeSet owningNode:owningNode traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout)]; + ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection]; + [self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout)]; // Step 2: Clone the new data ASElementMap *newMap = [mutableMap copy]; - _pendingMap = newMap; + self.pendingMap = newMap; // Step 3: Ask layout delegate for contexts id layoutContext = nil; @@ -585,7 +578,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * [_delegate dataController:self willUpdateWithChangeSet:changeSet]; // Step 5: Deploy the new data as "completed" and inform delegate - _visibleMap = newMap; + self.visibleMap = newMap; [_delegate dataController:self didUpdateWithChangeSet:changeSet]; }]; @@ -644,7 +637,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * */ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet - owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { @@ -656,7 +648,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * NSUInteger sectionCount = [self itemCountsFromDataSource].size(); if (sectionCount > 0) { NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertElementsIntoMap:map sections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map sections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } // Return immediately because reloadData can't be used in conjuntion with other updates. return; @@ -667,7 +659,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * // Aggressively repopulate supplementary nodes (#1773 & #1629) [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths changeSet:changeSet - owningNode:owningNode traitCollection:traitCollection indexPathsAreNew:NO shouldFetchSizeRanges:shouldFetchSizeRanges]; @@ -680,15 +671,14 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * } for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map sections:change.indexSet owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map sections:change.indexSet traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; // Aggressively reload supplementary nodes (#1773 & #1629) [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths changeSet:changeSet - owningNode:owningNode traitCollection:traitCollection indexPathsAreNew:YES shouldFetchSizeRanges:shouldFetchSizeRanges]; @@ -697,7 +687,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * - (void)_insertElementsIntoMap:(ASMutableElementMap *)map sections:(NSIndexSet *)sectionIndexes - owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { @@ -709,12 +698,12 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * // Items [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; // Supplementaries for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { // Step 2: Populate new elements for all sections - [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; + [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -729,10 +718,11 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * return; } + id dataSource = self.dataSource; for (ASCellNode *node in nodes) { ASSizeRange constrainedSize = [self constrainedSizeForElement:node.collectionElement inElementMap:_pendingMap]; [self _layoutNode:node withConstrainedSize:constrainedSize]; - BOOL matchesSize = [_dataSource dataController:self presentedSizeForElement:node.collectionElement matchesSize:node.frame.size]; + BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:node.collectionElement matchesSize:node.frame.size]; if (! matchesSize) { [nodesSizesChanged addObject:node]; } @@ -783,8 +773,9 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * // Can't update the trait collection right away because _visibleMap may not be up-to-date, // i.e there might be some elements that were allocated using the old trait collection but haven't been added to _visibleMap + [self _scheduleBlockOnMainSerialQueue:^{ - ASPrimitiveTraitCollection newTraitCollection = [[_environmentDelegate dataControllerEnvironment] primitiveTraitCollection]; + ASPrimitiveTraitCollection newTraitCollection = [self.node primitiveTraitCollection]; for (ASCollectionElement *element in _visibleMap) { element.traitCollection = newTraitCollection; } diff --git a/Source/Private/ASCellNode+Internal.h b/Source/Private/ASCellNode+Internal.h index efde08a758..89e741d238 100644 --- a/Source/Private/ASCellNode+Internal.h +++ b/Source/Private/ASCellNode+Internal.h @@ -72,9 +72,9 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes; -@property (weak, nullable) ASCollectionElement *collectionElement; +@property (atomic, weak, nullable) ASCollectionElement *collectionElement; -@property (nonatomic, weak, nullable) ASDisplayNode *owningNode; +@property (atomic, weak, nullable) id owningNode; @property (nonatomic, assign) BOOL shouldUseUIKitCell; diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index a950a2ff97..6c53fc8498 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -1,5 +1,5 @@ // -// ASTableViewTests.m +// ASTableViewTests.mm // Texture // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. @@ -61,7 +61,7 @@ - (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] eventLog:nil]; + return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] owningNode:nil eventLog:nil]; } - (ASTestDataController *)testDataController