From 54cde1a3dbe936d050eabb0324f4a93d4571029d Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 29 Feb 2016 00:54:36 -0800 Subject: [PATCH] Introduce ASIndexedNodeContext - It is a container object that holds enough information to construct and measure a cell node - All information is gathered on main thread. This allows ASDataController to capture the correct state of its data source before going to background. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++- .../Details/ASCollectionDataController.mm | 80 +++++------ .../Details/ASDataController+Subclasses.h | 12 +- AsyncDisplayKit/Details/ASDataController.mm | 133 ++++++++++-------- .../Details/ASIndexedNodeContext.h | 22 +++ .../Details/ASIndexedNodeContext.m | 26 ++++ 6 files changed, 185 insertions(+), 104 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASIndexedNodeContext.h create mode 100644 AsyncDisplayKit/Details/ASIndexedNodeContext.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 878bada747..230351bf50 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -504,6 +504,10 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; + E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; + E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */; }; + E5711A301C840C96009619D4 /* ASIndexedNodeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -659,7 +663,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -706,7 +710,7 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; @@ -826,6 +830,8 @@ DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; + E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = ""; }; + E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexedNodeContext.m; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1250,6 +1256,8 @@ 4640521A1A3F83C40061C0BA /* ASDataController.mm */, AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, + E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */, + E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */, ); name = "Data Controller"; sourceTree = ""; @@ -1327,6 +1335,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */, A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */, 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, @@ -1567,6 +1576,7 @@ 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, + E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */, @@ -1857,6 +1867,7 @@ 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, + E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.m in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, @@ -1965,6 +1976,7 @@ B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, + E5711A301C840C96009619D4 /* ASIndexedNodeContext.m in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..121098a6ad 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -13,6 +13,7 @@ #import "ASCellNode.h" #import "ASDisplayNodeInternal.h" #import "ASDataController+Subclasses.h" +#import "ASIndexedNodeContext.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -26,16 +27,14 @@ @end @implementation ASCollectionDataController { - NSMutableDictionary *> *_pendingNodes; - NSMutableDictionary *> *_pendingIndexPaths; + NSMutableDictionary *> *_pendingContexts; } - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled { self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; if (self != nil) { - _pendingNodes = [NSMutableDictionary dictionary]; - _pendingIndexPaths = [NSMutableDictionary dictionary]; + _pendingContexts = [NSMutableDictionary dictionary]; } return self; } @@ -44,20 +43,18 @@ { for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); - NSMutableArray *indexPaths = [NSMutableArray array]; - NSMutableArray *nodes = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withMutableContexts:contexts]; + _pendingContexts[kind] = contexts; // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:nil]; } } - (void)willReloadData { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { // Remove everything that existed before the reload, now that we're ready to insert replacements NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; @@ -74,11 +71,10 @@ } [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -86,31 +82,28 @@ { for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); - NSMutableArray *nodes = [NSMutableArray array]; - NSMutableArray *indexPaths = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; + _pendingContexts[kind] = contexts; // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:nil]; } } - (void)willInsertSections:(NSIndexSet *)sections { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -127,28 +120,25 @@ - (void)prepareForReloadSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSMutableArray *nodes = [NSMutableArray array]; - NSMutableArray *indexPaths = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; - _pendingNodes[kind] = nodes; - _pendingIndexPaths[kind] = indexPaths; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; + _pendingContexts[kind] = contexts; // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { - [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; + [_pendingContexts removeObjectForKey:kind]; }]; } @@ -169,7 +159,7 @@ } } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts { NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; for (NSUInteger i = 0; i < sectionCount; i++) { @@ -177,7 +167,7 @@ NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i]; for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; - [indexPaths addObject:indexPath]; + ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; @@ -185,19 +175,24 @@ ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = ^{ return supplementaryNode; }; } - [nodes addObject:supplementaryCellBlock]; + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize]; + [contexts addObject:context]; } } } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts { [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx]; NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [indexPaths addObject:indexPath]; + ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; @@ -205,7 +200,12 @@ ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = ^{ return supplementaryNode; }; } - [nodes addObject:supplementaryCellBlock]; + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize]; + [contexts addObject:context]; } }]; } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..ee9a51d254 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -9,7 +9,9 @@ #ifndef ASDataControllerSubclasses_Included #define ASDataControllerSubclasses_Included -//#import "ASDataController.h" +@class ASIndexedNodeContext; + +typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NSArray *indexPaths); @interface ASDataController (Subclasses) @@ -35,7 +37,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -43,7 +45,7 @@ * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance * to do so immediately on the main thread. */ -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind; /** * Provides the size range for a specific node during the layout process. @@ -55,12 +57,12 @@ /** * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; /** * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock; /** * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 32d62adac2..3597109900 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -19,6 +19,8 @@ #import "ASMainSerialQueue.h" #import "ASMultidimensionalArrayUtils.h" #import "ASThread.h" +#import "ASIndexedNodeContext.h" +#import "ASDataController+Subclasses.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -110,27 +112,28 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + NSUInteger count = contexts.count; // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; + for (NSUInteger i = 0; i < count; i += blockSize) { + NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); + NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; + [self _layoutNodesFromContexts:batchedContexts ofKind:kind completion:completionBlock]; } } -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { +- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind +{ NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); + ASDisplayNodeAssertTrue(nodes.count == contexts.count); - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { + [contexts enumerateObjectsUsingBlock:^(ASIndexedNodeContext *context, NSUInteger idx, __unused BOOL * stop) { ASCellNode *node = nodes[idx]; if (node.isNodeLoaded) { - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; + [self _layoutNode:node withConstrainedSize:context.constrainedSize]; } }]; } @@ -147,27 +150,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; /** * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. */ -- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)_batchLayoutNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; } -- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)_layoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock { - if (!nodes.count) { + if (!contexts.count) { return; } - NSUInteger nodeCount = nodes.count; + NSUInteger nodeCount = contexts.count; NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); - ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); - for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { - NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); + for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { + NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); __block NSArray *subarray; // Allocate nodes concurrently. @@ -176,13 +178,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(batchCount, queue, ^(size_t i) { unsigned long k = j + i; - ASCellNodeBlock cellBlock = nodes[k]; - ASCellNode *node = cellBlock(); + ASIndexedNodeContext *context = contexts[k]; + ASCellNodeBlock nodeBlock = context.nodeBlock; + ASCellNode *node = nodeBlock(); ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); allocatedNodeBuffer[i] = node; - if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; - } }); subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount]; @@ -200,11 +200,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:subarray fromContexts:[contexts subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind]; } else { allocationBlock(); [_mainSerialQueue performBlockOnMainThread:^{ - [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:subarray fromContexts:[contexts subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind]; }]; } @@ -216,7 +216,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Only measure nodes whose views aren't loaded, since we're in the background. // We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths: if (!node.isNodeLoaded) { - [self _layoutNode:node withConstrainedSize:nodeBoundSizes[k]]; + [self _layoutNode:node withConstrainedSize:contexts[k].constrainedSize]; } } }); @@ -224,9 +224,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); - free(nodeBoundSizes); - + if (completionBlock) { + NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeCount]; + [contexts enumerateObjectsUsingBlock:^(ASIndexedNodeContext * _Nonnull context, NSUInteger idx, BOOL * _Nonnull stop) { + [indexPaths addObject:context.indexPath]; + }]; + completionBlock(allocatedNodes, indexPaths); } } @@ -238,10 +242,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock { - if (indexPaths.count == 0) + if (indexPaths.count == 0) { return; + } NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); @@ -258,7 +263,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock { if (indexPaths.count == 0) { return; @@ -417,9 +422,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateFromEntireDataSourceWithMutableContexts:contexts]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -445,7 +449,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; if (completion) { dispatch_async(dispatch_get_main_queue(), completion); @@ -489,29 +493,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } /** - * Fetches row nodes and their specified index paths for the provided sections from the data source. + * Fetches row contexts for the provided sections from the data source. * * @discussion Results are stored in the passed mutable arrays. */ -- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableContexts:(NSMutableArray *)contexts { [indexSet 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]; - [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } }]; } /** - * Fetches row nodes and their specified index paths for all sections from the data source. + * Fetches row contexts for all sections from the data source. * * @discussion Results are stored in the passed mutable arrays. */ -- (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateFromEntireDataSourceWithMutableContexts:(NSMutableArray *)contexts { NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; for (NSUInteger i = 0; i < sectionNum; i++) { @@ -519,8 +526,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; for (NSUInteger j = 0; j < rowNum; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; - [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } } } @@ -607,9 +617,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableContexts:contexts]; [self prepareForInsertSections:sections]; @@ -623,7 +632,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -658,9 +668,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; - NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + NSMutableArray *contexts = [NSMutableArray array]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableContexts:contexts]; [self prepareForReloadSections:sections]; @@ -674,7 +683,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -768,14 +777,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceWithBlock:^{ // 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]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:sortedIndexPaths[i]]]; + NSIndexPath *indexPath = sortedIndexPaths[i]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; @@ -810,20 +825,24 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; }]; diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/AsyncDisplayKit/Details/ASIndexedNodeContext.h new file mode 100644 index 0000000000..7039d1f014 --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.h @@ -0,0 +1,22 @@ +// +// ASIndexedNodeContext.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 2/28/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASIndexedNodeContext : NSObject + +@property (nonatomic, readonly, strong) ASCellNodeBlock nodeBlock; +@property (nonatomic, readonly, assign) NSIndexPath *indexPath; +@property (nonatomic, readonly, assign) ASSizeRange constrainedSize; + +- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock + indexPath:(NSIndexPath *)indexPath + constrainedSize:(ASSizeRange)constrainedSize; + +@end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.m b/AsyncDisplayKit/Details/ASIndexedNodeContext.m new file mode 100644 index 0000000000..f7ec9571ea --- /dev/null +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.m @@ -0,0 +1,26 @@ +// +// ASIndexedNodeContext.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 2/28/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASIndexedNodeContext.h" + +@implementation ASIndexedNodeContext + +- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock + indexPath:(NSIndexPath *)indexPath + constrainedSize:(ASSizeRange)constrainedSize; +{ + self = [super init]; + if (self) { + _nodeBlock = nodeBlock; + _indexPath = indexPath; + _constrainedSize = constrainedSize; + } + return self; +} + +@end