diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 8cf72597a3..5387aa6756 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -60,6 +60,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6ea8c3a8..e9351912f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1b06d6befd..dfc5dde0c9 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -719,19 +719,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths { - if (indexPaths == nil) { - return nil; - } - - NSMutableArray *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToCollectionNode:indexPathInView]; + })); } - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath @@ -747,17 +737,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; - NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - for (NSIndexPath *indexPath in indexPaths) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node) { - // It is possible for UICollectionView to return indexPaths before the node is completed. - [visibleNodes addObject:node]; - } - } - - return visibleNodes; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({ + [self nodeForItemAtIndexPath:indexPath]; + })); } - (BOOL)usesSynchronousDataLoading @@ -2176,13 +2159,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return; } - NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; - for (ASCellNode *node in nodes) { - NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; - if (uikitIndexPath != nil) { - [uikitIndexPaths addObject:uikitIndexPath]; - } - } + NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({ + [self indexPathForNode:node]; + })); [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index b52f948212..d4a0ba1157 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -158,14 +158,12 @@ - (void)setupYogaCalculatedLayout { YGNodeRef yogaNode = self.style.yogaNode; - uint32_t childCount = YGNodeGetChildCount(yogaNode); - ASDisplayNodeAssert(childCount == self.yogaChildren.count, + ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count, @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; - for (ASDisplayNode *subnode in self.yogaChildren) { - [sublayouts addObject:[subnode layoutForYogaNode]]; - } + NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({ + [subnode layoutForYogaNode]; + })); // The layout for self should have position CGPointNull, but include the calculated size. CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index f78a688ea8..fc0a2dbbee 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -607,15 +607,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return nil; } - NSMutableArray *indexPathsArray = [NSMutableArray new]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToTableNode:indexPathInView]; + })); } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 9eb1ec0b0f..37749e53de 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -132,22 +132,6 @@ #define __has_attribute(x) 0 // Compatibility with non-clang compilers. #endif -#ifndef NS_CONSUMED -#if __has_feature(attribute_ns_consumed) -#define NS_CONSUMED __attribute__((ns_consumed)) -#else -#define NS_CONSUMED -#endif -#endif - -#ifndef NS_RETURNS_RETAINED -#if __has_feature(attribute_ns_returns_retained) -#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) -#else -#define NS_RETURNS_RETAINED -#endif -#endif - #ifndef CF_RETURNS_RETAINED #if __has_feature(attribute_cf_returns_retained) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) @@ -245,26 +229,38 @@ * Create a new set by mapping `collection` over `work`, ignoring nil. */ #define ASSetByFlatMapping(collection, decl, work) ({ \ - NSMutableSet *s = [NSMutableSet set]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [s addObject:result]; \ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ } \ } \ - s; \ + NSSet *result; \ + if (_i == 0) { \ + /** Zero fast path. */ \ + result = [NSSet set]; \ + } else if (_i == 1) { \ + /** NSSingleObjectSet is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSSet setWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFSetCallBacks cb = kCFTypeSetCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ }) /** * Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. */ #define ASPointerTableByFlatMapping(collection, decl, work) ({ \ - NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ + NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [t addObject:result]; \ - } \ + /* NSHashTable accepts nil and avoid extra retain/release. */ \ + [t addObject:work]; \ } \ t; \ }) @@ -273,12 +269,37 @@ * Create a new array by mapping `collection` over `work`, ignoring nil. */ #define ASArrayByFlatMapping(collection, decl, work) ({ \ - NSMutableArray *a = [NSMutableArray array]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [a addObject:result]; \ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ } \ } \ - a; \ + NSArray *result; \ + if (_i == 0) { \ + /** Zero array fast path. */ \ + result = @[]; \ + } else if (_i == 1) { \ + /** NSSingleObjectArray is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSArray arrayWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ +}) + +#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ + id _cArray[collection.count]; \ + NSUInteger _i = 0; \ + for (decl in collection) {\ + if ((_cArray[_i] = work)) { \ + _i++; \ + } \ + } \ + [[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ }) diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m index 548fa01816..77e0922261 100644 --- a/Source/Details/ASCollectionFlowLayoutDelegate.m +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -59,7 +59,7 @@ + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context { ASElementMap *elements = context.elements; - NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index 2734e9649a..482123c516 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -102,9 +102,9 @@ return [[ASCollectionLayoutState alloc] initWithContext:context]; } - NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); + NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, + ASCollectionElement *element, + [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index eed7eff262..a73f80a92e 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,6 +31,11 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject +/** + * The total number of elements in this map. + */ +@property (readonly) NSUInteger count; + /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index 08e90d401e..f5646de78a 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,6 +75,11 @@ return self; } +- (NSUInteger)count +{ + return _elementToIndexPathMap.count; +} + - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index f6fbe91279..226b4a89e0 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -203,13 +203,9 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( _sublayoutLayoutElements = nil; } else { // Add sublayouts layout elements to an internal array to retain it while the layout lives - NSUInteger sublayoutCount = _sublayouts.count; - if (sublayoutCount > 0) { - _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; - for (ASLayout *sublayout in _sublayouts) { - [_sublayoutLayoutElements addObject:sublayout.layoutElement]; - } - } + _sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ + sublayout.layoutElement; + })); } } } @@ -236,7 +232,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( queue.push_back({sublayout, sublayout.position}); } - NSMutableArray *flattenedSublayouts = [NSMutableArray array]; + std::vector flattenedSublayouts; while (!queue.empty()) { const Context context = queue.front(); @@ -255,7 +251,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( position:absolutePosition sublayouts:@[]]; } - [flattenedSublayouts addObject:layout]; + flattenedSublayouts.push_back(layout); } else if (sublayoutsCount > 0){ std::vector sublayoutContexts; for (ASLayout *sublayout in sublayouts) { @@ -265,7 +261,8 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( } } - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; + NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. layout.retainSublayoutLayoutElements = YES; diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 76901d1ac7..a6aabdb399 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -295,19 +295,15 @@ ASLayoutElementStyleExtensibilityForwarding - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { - NSArray *children = self.children; - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; - CGSize size = constrainedSize.min; - for (id child in children) { + NSArray *sublayouts = ASArrayByFlatMapping(self.children, id child, ({ ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; sublayout.position = CGPointZero; size.width = MAX(size.width, sublayout.size.width); size.height = MAX(size.height, sublayout.size.height); - - [sublayouts addObject:sublayout]; - } + sublayout; + })); return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index 71a48d450b..b21aeb41b6 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -27,13 +27,10 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - NSInteger i = 0; - for (NSArray *subarray in array) { + return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({ ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - newArray[i++] = [subarray mutableCopy]; - } - return newArray; + [subarray mutableCopy]; + })); } void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index da6350a4ab..b664feaf55 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -69,11 +69,9 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi - (NSArray *)subnodesForKey:(NSString *)key { - NSMutableArray *subnodes = [NSMutableArray array]; - for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { - [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; - } - return subnodes; + return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({ + (ASDisplayNode *)sublayout.layoutElement; + })); } - (NSArray *)insertedSubnodes