From 2f999517328cc6e30be2cbad57cdba605526faf5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 27 Sep 2016 16:41:45 -0700 Subject: [PATCH] [Layout] Move [ASLayoutSpec children] from std::map to NSMutableArray (#2253) * Initial commit to move [ASLayoutSpec children] from std::map to NSMutableArray * Add NSFastEnumeration to ASLayoutable * ASNullLayoutSpec is a Singleton now * Move ASLayoutSpecPrivate in Private folder * Move to NSArrayPointer and remove ASNullLayoutSpec * Revert "Move to NSArrayPointer and remove ASNullLayoutSpec" This reverts commit 9ab9cf7024b1f6e1984d84fe58af2b84e84cdf94. * Move to childAtIndex: and setChild:atIndex: --- AsyncDisplayKit.xcodeproj/project.pbxproj | 22 ++- AsyncDisplayKit/ASDisplayNode.mm | 7 + .../Layout/ASBackgroundLayoutSpec.h | 9 +- .../Layout/ASBackgroundLayoutSpec.mm | 28 ++-- .../Layout/ASLayoutSpec+Subclasses.h | 64 +++++++++ .../Layout/ASLayoutSpec+Subclasses.mm | 110 ++++++++++++++ AsyncDisplayKit/Layout/ASLayoutSpec.h | 29 ---- AsyncDisplayKit/Layout/ASLayoutSpec.mm | 135 ++++++------------ AsyncDisplayKit/Layout/ASLayoutable.h | 2 +- AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h | 9 ++ AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm | 35 +++-- AsyncDisplayKit/Private/ASLayoutSpecPrivate.h | 25 ++++ AsyncDisplayKitTests/ASDisplayNodeTests.m | 20 +++ 13 files changed, 341 insertions(+), 154 deletions(-) create mode 100644 AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h create mode 100644 AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm create mode 100644 AsyncDisplayKit/Private/ASLayoutSpecPrivate.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 9ff09ca859..d5699da155 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -206,6 +206,9 @@ 69708BA61D76386D005C3CF9 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */; }; 69708BA71D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */; }; 69708BA81D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */; }; + 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */; }; + 697796601D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; + 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */; }; 697C0DE41CF38F28001DE0D4 /* ASLayoutValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */; }; 697C0DE51CF38F28001DE0D4 /* ASLayoutValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 697C0DE21CF38F28001DE0D4 /* ASLayoutValidation.mm */; }; @@ -220,6 +223,7 @@ 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; 69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; + 69EEA0A11D9AB43900B46420 /* ASLayoutSpecPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69FEE53D1D95A9AF0086F066 /* ASLayoutableStyleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 69FEE53C1D95A9AF0086F066 /* ASLayoutableStyleTests.m */; }; 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -985,6 +989,8 @@ 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBackgroundLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = ""; }; 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASEqualityHashHelpers.mm; path = TextKit/ASEqualityHashHelpers.mm; sourceTree = ""; }; + 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASLayoutSpec+Subclasses.h"; path = "AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h"; sourceTree = ""; }; + 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASLayoutSpec+Subclasses.mm"; path = "AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEditableTextNodeTests.m; sourceTree = ""; }; 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutValidation.h; path = AsyncDisplayKit/Layout/ASLayoutValidation.h; sourceTree = ""; }; 697C0DE21CF38F28001DE0D4 /* ASLayoutValidation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutValidation.mm; path = AsyncDisplayKit/Layout/ASLayoutValidation.mm; sourceTree = ""; }; @@ -996,6 +1002,7 @@ 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = ""; }; 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = ""; }; 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = ""; }; + 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecPrivate.h; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; 69FEE53C1D95A9AF0086F066 /* ASLayoutableStyleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutableStyleTests.m; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; @@ -1527,9 +1534,6 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( - CC446A2D1D80AAE00071FD03 /* ASObjectDescriptionHelpers.h */, - CC446A2E1D80AAE00071FD03 /* ASObjectDescriptionHelpers.m */, - CC54A81B1D70077A00296A24 /* ASDispatch.h */, 058D0A02195D050800B7D73C /* _AS-objc-internal.h */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, @@ -1548,6 +1552,7 @@ 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */, + CC54A81B1D70077A00296A24 /* ASDispatch.h */, 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, @@ -1563,11 +1568,14 @@ 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */, + 69EEA0A01D9AB43900B46420 /* ASLayoutSpecPrivate.h */, ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */, E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */, E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */, 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */, 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, + CC446A2D1D80AAE00071FD03 /* ASObjectDescriptionHelpers.h */, + CC446A2E1D80AAE00071FD03 /* ASObjectDescriptionHelpers.m */, CC3B20811C3F76D600798563 /* ASPendingStateController.h */, CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, 058D0A10195D050800B7D73C /* ASSentinel.h */, @@ -1677,12 +1685,14 @@ ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */, + 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */, ACF6ED111B17843500DA7C62 /* ASLayoutable.h */, E55D86311CA8A14000A0C26F /* ASLayoutable.mm */, 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */, - 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */, ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */, ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */, + 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */, + 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */, 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */, 697C0DE21CF38F28001DE0D4 /* ASLayoutValidation.mm */, ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */, @@ -1775,6 +1785,7 @@ 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, + 69EEA0A11D9AB43900B46420 /* ASLayoutSpecPrivate.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, @@ -1819,6 +1830,7 @@ AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, + 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */, 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */, B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, @@ -2227,6 +2239,7 @@ 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */, 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */, + 697796601D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */, @@ -2411,6 +2424,7 @@ 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, + 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 6224fe8406..841441adb3 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -3364,6 +3364,13 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; } } +#pragma mark - NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len +{ + return [self.subnodes countByEnumeratingWithState:state objects:buffer count:len]; +} + #pragma mark - ASEnvironment - (ASEnvironmentState)environmentState diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h index 7d02f100df..9e39086afa 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h @@ -17,11 +17,16 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASBackgroundLayoutSpec : ASLayoutSpec +/** + * Background layoutable for this layout spec + */ @property (nullable, nonatomic, strong) id background; /** - @param child A child that is laid out to determine the size of this spec. - @param background A layoutable object that is laid out behind the child. If this is nil, the background is omitted. + * Creates and returns an ASBackgroundLayoutSpec object + * + * @param child A child that is laid out to determine the size of this spec. + * @param background A layoutable object that is laid out behind the child. If this is nil, the background is omitted. */ + (instancetype)backgroundLayoutSpecWithChild:(id)child background:(nullable id)background; diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm index f90acb36cd..ecba89ab82 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm @@ -9,6 +9,7 @@ // #import "ASBackgroundLayoutSpec.h" +#import "ASLayoutSpec+Subclasses.h" #import "ASAssert.h" #import "ASLayout.h" @@ -16,11 +17,17 @@ static NSUInteger const kForegroundChildIndex = 0; static NSUInteger const kBackgroundChildIndex = 1; -@interface ASBackgroundLayoutSpec () -@end - @implementation ASBackgroundLayoutSpec +#pragma mark - Class + ++ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background; +{ + return [[self alloc] initWithChild:child background:background]; +} + +#pragma mark - Lifecycle + - (instancetype)initWithChild:(id)child background:(id)background { if (!(self = [super init])) { @@ -28,15 +35,12 @@ static NSUInteger const kBackgroundChildIndex = 1; } ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - [self setChild:child forIndex:kForegroundChildIndex]; + [self setChild:child atIndex:kForegroundChildIndex]; self.background = background; return self; } -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background; -{ - return [[self alloc] initWithChild:child background:background]; -} +#pragma mark - ASLayoutSpec /** * First layout the contents, then fit the background image. @@ -45,7 +49,7 @@ static NSUInteger const kBackgroundChildIndex = 1; restrictedToSize:(ASLayoutableSize)size relativeToParentSize:(CGSize)parentSize { - ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; + ASLayout *contentsLayout = [[super childAtIndex:kForegroundChildIndex] layoutThatFits:constrainedSize parentSize:parentSize]; NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:2]; if (self.background) { @@ -61,14 +65,16 @@ static NSUInteger const kBackgroundChildIndex = 1; return [ASLayout layoutWithLayoutable:self size:contentsLayout.size sublayouts:sublayouts]; } +#pragma mark - Background + - (void)setBackground:(id)background { - [super setChild:background forIndex:kBackgroundChildIndex]; + [super setChild:background atIndex:kBackgroundChildIndex]; } - (id)background { - return [super childForIndex:kBackgroundChildIndex]; + return [super childAtIndex:kBackgroundChildIndex]; } @end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h b/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h new file mode 100644 index 0000000000..f9ea6d0d95 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.h @@ -0,0 +1,64 @@ +// +// ASLayoutSpec+Subclasses.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 9/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASLayoutSpec (Subclassing) + +/** + * Helper method for finalLayoutable support + * + * @warning If you are getting recursion crashes here after implementing finalLayoutable, make sure + * that you are setting isFinalLayoutable flag to YES. This must be one BEFORE adding a child + * to the new ASLayoutable. + * + * For example: + * - (id)finalLayoutable + * { + * ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; + * insetSpec.insets = UIEdgeInsetsMake(10,10,10,10); + * insetSpec.isFinalLayoutable = YES; + * [insetSpec setChild:self]; + * return insetSpec; + * } + * + * @see finalLayoutable + */ +- (id)layoutableToAddFromLayoutable:(id)child; + +/** + * Adds a child with the given identifier to this layout spec. + * + * @param child A child to be added. + * + * @param index An index associated with the child. + * + * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the + * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, + * only require a single child. + * + * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) + * a subclass can use the setChild method to set the "primary" child. It should then use this method + * to set any other required children. Ideally a subclass would hide this from the user, and use the + * setChild:forIndex: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild + * property that behind the scenes is calling setChild:forIndex:. + */ +- (void)setChild:(id)child atIndex:(NSUInteger)index; + +/** + * Returns the child added to this layout spec using the given index. + * + * @param index An identifier associated with the the child. + */ +- (nullable id)childAtIndex:(NSUInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm b/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm new file mode 100644 index 0000000000..61f4f86714 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASLayoutSpec+Subclasses.mm @@ -0,0 +1,110 @@ +// +// ASLayoutSpec+Subclasses.m +// AsyncDisplayKit +// +// Created by Michael Schneider on 9/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASLayoutSpec+Subclasses.h" +#import "ASLayoutSpec.h" +#import "ASLayoutSpecPrivate.h" + +#pragma mark - ASNullLayoutSpec + +@interface ASNullLayoutSpec : ASLayoutSpec +- (instancetype)init __unavailable; ++ (ASNullLayoutSpec *)null; +@end + +@implementation ASNullLayoutSpec : ASLayoutSpec + ++ (ASNullLayoutSpec *)null +{ + static ASNullLayoutSpec *sharedNullLayoutSpec = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedNullLayoutSpec = [[self alloc] init]; + }); + return sharedNullLayoutSpec; +} + +- (BOOL)isMutable +{ + return NO; +} + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + return [ASLayout layoutWithLayoutable:self size:CGSizeZero]; +} + +@end + + +#pragma mark - ASLayoutSpec (Subclassing) + +@implementation ASLayoutSpec (Subclassing) + +#pragma mark - Final layoutable + +- (id)layoutableToAddFromLayoutable:(id)child +{ + if (self.isFinalLayoutable == NO) { + id finalLayoutable = [child finalLayoutable]; + if (finalLayoutable != child) { + if (ASEnvironmentStatePropagationEnabled()) { + ASEnvironmentStatePropagateUp(finalLayoutable, child.environmentState.layoutOptionsState); + } else { + // If state propagation is not enabled the layout options state needs to be copied manually + ASEnvironmentState finalLayoutableEnvironmentState = finalLayoutable.environmentState; + finalLayoutableEnvironmentState.layoutOptionsState = child.environmentState.layoutOptionsState; + finalLayoutable.environmentState = finalLayoutableEnvironmentState; + } + return finalLayoutable; + } + } + return child; +} + +#pragma mark - Child with index + +- (void)setChild:(id)child atIndex:(NSUInteger)index +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + + id layoutable = child ? [self layoutableToAddFromLayoutable:child] : [ASNullLayoutSpec null]; + + if (child) { + if (_childrenArray.count < index) { + // Fill up the array with null objects until the index + NSInteger i = _childrenArray.count; + while (i < index) { + _childrenArray[i] = [ASNullLayoutSpec null]; + i++; + } + } + } + + // Replace object at the given index with the layoutable + _childrenArray[index] = layoutable; + + // TODO: Should we propagate up the layoutable at it could happen that multiple children will propagated up their + // layout options and one child will overwrite values from another child + // [self propagateUpLayoutable:finalLayoutable]; +} + +- (id)childAtIndex:(NSUInteger)index +{ + id layoutable = nil; + if (index < _childrenArray.count) { + layoutable = _childrenArray[index]; + } + + // Null layoutable should not be accessed + ASDisplayNodeAssert(layoutable != [ASNullLayoutSpec null], @"Access child at index without set a child at that index"); + + return layoutable; +} + +@end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index 371d72c374..30d49d8061 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -25,8 +25,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL isMutable; -- (instancetype)init; - /** * Parent of the layout spec */ @@ -49,25 +47,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, strong, nonatomic) id child; -/** - * Adds a child with the given identifier to this layout spec. - * - * @param child A child to be added. - * - * @param index An index associated with the child. - * - * @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the - * responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec, - * only require a single child. - * - * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) - * a subclass can use the setChild method to set the "primary" child. It should then use this method - * to set any other required children. Ideally a subclass would hide this from the user, and use the - * setChild:forIndex: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild - * property that behind the scenes is calling setChild:forIndex:. - */ -- (void)setChild:(id)child forIndex:(NSUInteger)index; - /** * Adds childen to this layout spec. * @@ -81,13 +60,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, strong, nonatomic) NSArray> *children; -/** - * Returns the child added to this layout spec using the given index. - * - * @param index An identifier associated withe the child. - */ -- (nullable id)childForIndex:(NSUInteger)index; - @end /** @@ -122,4 +94,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index eed3598711..c4126f8aaa 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -9,28 +9,8 @@ // #import "ASLayoutSpec.h" - -#import "ASAssert.h" -#import "ASInternalHelpers.h" -#import "ASEnvironmentInternal.h" - -#import "ASLayout.h" -#import "ASThread.h" -#import "ASTraitCollection.h" - -#import -#import -#import - -typedef std::map, std::less> ASChildMap; - -@interface ASLayoutSpec() { - ASDN::RecursiveMutex __instanceLock__; - ASChildMap _children; - ASEnvironmentState _environmentState; - ASLayoutableStyle *_style; -} -@end +#import "ASLayoutSpecPrivate.h" +#import "ASLayoutSpec+Subclasses.h" @implementation ASLayoutSpec @@ -60,6 +40,7 @@ typedef std::map, std::less> ASCh _isMutable = YES; _environmentState = ASEnvironmentStateMakeDefault(); _style = [[ASLayoutableStyle alloc] init]; + _childrenArray = [[NSMutableArray alloc] init]; return self; } @@ -74,6 +55,13 @@ typedef std::map, std::less> ASCh return YES; } +#pragma mark - Final Layoutable + +- (id)finalLayoutable +{ + return self; +} + #pragma mark - Style - (ASLayoutableStyle *)style @@ -113,44 +101,8 @@ typedef std::map, std::less> ASCh return [ASLayout layoutWithLayoutable:self size:constrainedSize.min]; } -- (id)finalLayoutable -{ - return self; -} -- (id)layoutableToAddFromLayoutable:(id)child -{ - if (self.isFinalLayoutable == NO) { - - // If you are getting recursion crashes here after implementing finalLayoutable, make sure - // that you are setting isFinalLayoutable flag to YES. This must be one BEFORE adding a child - // to the new ASLayoutable. - // - // For example: - //- (id)finalLayoutable - //{ - // ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; - // insetSpec.insets = UIEdgeInsetsMake(10,10,10,10); - // insetSpec.isFinalLayoutable = YES; - // [insetSpec setChild:self]; - // return insetSpec; - //} - - id finalLayoutable = [child finalLayoutable]; - if (finalLayoutable != child) { - if (ASEnvironmentStatePropagationEnabled()) { - ASEnvironmentStatePropagateUp(finalLayoutable, child.environmentState.layoutOptionsState); - } else { - // If state propagation is not enabled the layout options state needs to be copied manually - ASEnvironmentState finalLayoutableEnvironmentState = finalLayoutable.environmentState; - finalLayoutableEnvironmentState.layoutOptionsState = child.environmentState.layoutOptionsState; - finalLayoutable.environmentState = finalLayoutableEnvironmentState; - } - return finalLayoutable; - } - } - return child; -} +#pragma mark - Parent - (void)setParent:(id)parent { @@ -162,67 +114,63 @@ typedef std::map, std::less> ASCh } } + +#pragma mark - Child + - (void)setChild:(id)child { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");; + ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); + if (child) { id finalLayoutable = [self layoutableToAddFromLayoutable:child]; if (finalLayoutable) { - _children[0] = finalLayoutable; + _childrenArray[0] = finalLayoutable; [self propagateUpLayoutable:finalLayoutable]; } } else { - _children.erase(0); + if (_childrenArray.count) { + [_childrenArray removeObjectAtIndex:0]; + } } } -- (void)setChild:(id)child forIndex:(NSUInteger)index +- (id)child { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (child) { - id finalLayoutable = [self layoutableToAddFromLayoutable:child]; - _children[index] = finalLayoutable; - } else { - _children.erase(index); + ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); + + if (_childrenArray.count) { + return _childrenArray[0]; } - // TODO: Should we propagate up the layoutable at it could happen that multiple children will propagated up their - // layout options and one child will overwrite values from another child - // [self propagateUpLayoutable:finalLayoutable]; + + return nil; } +#pragma mark - Children + - (void)setChildren:(NSArray> *)children { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _children.clear(); + [_childrenArray removeAllObjects]; + NSUInteger i = 0; for (id child in children) { - _children[i] = [self layoutableToAddFromLayoutable:child]; + _childrenArray[i] = [self layoutableToAddFromLayoutable:child]; i += 1; } } -- (id)childForIndex:(NSUInteger)index -{ - if (index < _children.size()) { - return _children[index]; - } - return nil; -} - -- (id)child -{ - return _children[0]; -} - - (NSArray *)children { - std::vector children; - for (ASChildMap::iterator it = _children.begin(); it != _children.end(); ++it ) { - children.push_back(it->second); - } - - return [NSArray arrayWithObjects:&children[0] count:children.size()]; + return [_childrenArray copy]; +} + +#pragma mark - NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len +{ + return [_childrenArray countByEnumeratingWithState:state objects:buffer count:len]; } #pragma mark - ASEnvironment @@ -279,7 +227,6 @@ ASEnvironmentLayoutExtensibilityForwarding @end - #pragma mark - ASWrapperLayoutSpec @implementation ASWrapperLayoutSpec diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 979ff38126..15423cc097 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -50,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN * access to the options via convenience properties. If you are creating custom layout spec, then you can * extend the backing layout options class to accommodate any new layout options. */ -@protocol ASLayoutable +@protocol ASLayoutable #pragma mark - Getter diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h index e6b7c62f8b..7687f55288 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.h @@ -17,8 +17,17 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASOverlayLayoutSpec : ASLayoutSpec +/** + * Overlay layoutable of this layout spec + */ @property (nullable, nonatomic, strong) id overlay; +/** + * Creates and returns an ASOverlayLayoutSpec object with a given child and an layoutable that act as overlay. + * + * @param child A child that is laid out to determine the size of this spec. + * @param overlay A layoutable object that is laid out over the child. If this is nil, the overlay is omitted. + */ + (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(nullable id)overlay; @end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm index 81a261bd7e..571c84ad44 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm @@ -9,6 +9,7 @@ // #import "ASOverlayLayoutSpec.h" +#import "ASLayoutSpec+Subclasses.h" #import "ASAssert.h" #import "ASLayout.h" @@ -18,32 +19,40 @@ static NSUInteger const kOverlayChildIndex = 1; @implementation ASOverlayLayoutSpec -- (instancetype)initWithChild:(id)child overlay:(id)overlay -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); - self.overlay = overlay; - [self setChild:child forIndex:kUnderlayChildIndex]; - return self; -} +#pragma mark - Class + (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay { return [[self alloc] initWithChild:child overlay:overlay]; } +#pragma mark - Lifecycle + +- (instancetype)initWithChild:(id)child overlay:(id)overlay +{ + if (!(self = [super init])) { + return nil; + } + ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); + [self setChild:child atIndex:kUnderlayChildIndex]; + self.overlay = overlay; + return self; +} + +#pragma mark - Setter / Getter + - (void)setOverlay:(id)overlay { - [super setChild:overlay forIndex:kOverlayChildIndex]; + [super setChild:overlay atIndex:kOverlayChildIndex]; } - (id)overlay { - return [super childForIndex:kOverlayChildIndex]; + return [super childAtIndex:kOverlayChildIndex]; } +#pragma mark - ASLayoutSpec + /** First layout the contents, then fit the overlay on top of it. */ @@ -51,7 +60,7 @@ static NSUInteger const kOverlayChildIndex = 1; restrictedToSize:(ASLayoutableSize)size relativeToParentSize:(CGSize)parentSize { - ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; + ASLayout *contentsLayout = [[super childAtIndex:kUnderlayChildIndex] layoutThatFits:constrainedSize parentSize:parentSize]; contentsLayout.position = CGPointZero; NSMutableArray *sublayouts = [NSMutableArray arrayWithObject:contentsLayout]; if (self.overlay) { diff --git a/AsyncDisplayKit/Private/ASLayoutSpecPrivate.h b/AsyncDisplayKit/Private/ASLayoutSpecPrivate.h new file mode 100644 index 0000000000..d4a55fce43 --- /dev/null +++ b/AsyncDisplayKit/Private/ASLayoutSpecPrivate.h @@ -0,0 +1,25 @@ +// +// ASLayoutSpecPrivate.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 9/15/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + + +#import "ASInternalHelpers.h" +#import "ASEnvironmentInternal.h" +#import "ASThread.h" + +@interface ASLayoutSpec() { + ASDN::RecursiveMutex __instanceLock__; + ASEnvironmentState _environmentState; + ASLayoutableStyle *_style; + NSMutableArray *_childrenArray; +} +@end + diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index ae36bb962c..642c1967e5 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2070,4 +2070,24 @@ static bool stringContainsPointer(NSString *description, id p) { #pragma clang diagnostic pop } +- (void)testSubnodesFastEnumeration +{ + DeclareNodeNamed(parentNode); + DeclareNodeNamed(a); + DeclareNodeNamed(b); + DeclareNodeNamed(c); + DeclareViewNamed(d); + + NSArray *subnodes = @[a, b, c, d]; + for (ASDisplayNode *node in subnodes) { + [parentNode addSubnode:node]; + } + + NSInteger i = 0; + for (ASDisplayNode *subnode in parentNode.subnodes) { + XCTAssertEqualObjects(subnode, subnodes[i]); + i++; + } +} + @end