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.
This commit is contained in:
Huy Nguyen 2016-02-29 00:54:36 -08:00
parent 3671f5cbc5
commit 54cde1a3db
6 changed files with 185 additions and 104 deletions

View File

@ -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 = "<group>"; };
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; };
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = "<group>"; };
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; };
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
@ -706,7 +710,7 @@
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = "<group>"; };
430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = "<group>"; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = "<group>"; };
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = "<group>"; };
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
@ -826,6 +830,8 @@
DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = "<group>"; };
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = "<group>"; };
E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexedNodeContext.m; sourceTree = "<group>"; };
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 = "<group>"; };
/* 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 = "<group>";
@ -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 */,

View File

@ -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<NSString *, NSMutableArray<ASCellNode *> *> *_pendingNodes;
NSMutableDictionary<NSString *, NSMutableArray<NSIndexPath *> *> *_pendingIndexPaths;
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *)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];
}
}];
}

View File

@ -9,7 +9,9 @@
#ifndef ASDataControllerSubclasses_Included
#define ASDataControllerSubclasses_Included
//#import "ASDataController.h"
@class ASIndexedNodeContext;
typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *)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<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)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.

View File

@ -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<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *batchedContexts = [contexts subarrayWithRange:batchedRange];
[self _layoutNodesFromContexts:batchedContexts ofKind:kind completion:completionBlock];
}
}
- (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
- (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
[self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
{
if (!nodes.count) {
if (!contexts.count) {
return;
}
NSUInteger nodeCount = nodes.count;
NSUInteger nodeCount = contexts.count;
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray<ASCellNode *> 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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *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];
}];
}];
}];

View File

@ -0,0 +1,22 @@
//
// ASIndexedNodeContext.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 2/28/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASDimension.h>
@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

View File

@ -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