From 99b674c346a4b538483c38c4940ef8f9ad76d3ea Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 22:40:42 -0800 Subject: [PATCH] Lay some foundation for our new pending state controller --- AsyncDisplayKit.xcodeproj/project.pbxproj | 48 ++ AsyncDisplayKit/ASDisplayNode.mm | 19 +- .../Private/ASDisplayNodeInternal.h | 2 + .../Private/ASPendingStateController.h | 45 ++ .../Private/ASPendingStateController.mm | 99 ++++ AsyncDisplayKit/Private/ASWeakSet.h | 40 ++ AsyncDisplayKit/Private/ASWeakSet.m | 68 +++ AsyncDisplayKit/Private/_ASPendingState.h | 4 + AsyncDisplayKit/Private/_ASPendingState.m | 446 ++++++++---------- .../ASPendingStateControllerTests.m | 40 ++ AsyncDisplayKitTests/ASWeakSetTests.m | 134 ++++++ 11 files changed, 677 insertions(+), 268 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASPendingStateController.h create mode 100644 AsyncDisplayKit/Private/ASPendingStateController.mm create mode 100644 AsyncDisplayKit/Private/ASWeakSet.h create mode 100644 AsyncDisplayKit/Private/ASWeakSet.m create mode 100644 AsyncDisplayKitTests/ASPendingStateControllerTests.m create mode 100644 AsyncDisplayKitTests/ASWeakSetTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e57d0a3ff2..3a116b914d 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -455,6 +455,16 @@ B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; C78F7E2A1BF7808300CDEAFC /* ASTableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F880591BEAEC7500D17647 /* ASTableNode.m */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; + CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; + CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; + CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; + CC3B20891C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; + CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; + CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; + CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; + CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; }; + CC3B20901C3F892D00798563 /* ASPendingStateControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASPendingStateControllerTests.m */; }; CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; @@ -786,6 +796,12 @@ B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; + CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = ""; }; + CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = ""; }; + CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = ""; }; + CC3B20881C3F7A5400798563 /* ASWeakSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSet.m; sourceTree = ""; }; + CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = ""; }; + CC3B208F1C3F892D00798563 /* ASPendingStateControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPendingStateControllerTests.m; sourceTree = ""; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; @@ -1002,6 +1018,8 @@ children = ( DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, + CC3B208F1C3F892D00798563 /* ASPendingStateControllerTests.m */, + CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, @@ -1123,6 +1141,10 @@ children = ( DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */, DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */, + CC3B20811C3F76D600798563 /* ASPendingStateController.h */, + CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, + CC3B20871C3F7A5400798563 /* ASWeakSet.h */, + CC3B20881C3F7A5400798563 /* ASWeakSet.m */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */, @@ -1413,6 +1435,7 @@ 257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */, ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, 257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */, + CC3B20891C3F7A5400798563 /* ASWeakSet.h in Headers */, ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, @@ -1421,6 +1444,7 @@ 257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */, B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */, 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, + CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */, 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */, 058D0A81195D05F900B7D73C /* ASThread.h in Headers */, ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */, @@ -1475,6 +1499,7 @@ B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, + CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, @@ -1517,6 +1542,7 @@ 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, + CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */, DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, @@ -1611,6 +1637,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 78B4649EC16385BFAFD84D49 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1740,6 +1767,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 78B4649EC16385BFAFD84D49 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1799,6 +1841,7 @@ 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */, 058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */, 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */, + CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */, 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */, 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */, ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, @@ -1815,6 +1858,7 @@ 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */, + CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */, ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, 257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */, @@ -1861,6 +1905,7 @@ ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */, 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */, 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */, + CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */, @@ -1883,6 +1928,7 @@ AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, + CC3B20901C3F892D00798563 /* ASPendingStateControllerTests.m in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, @@ -1936,6 +1982,7 @@ B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */, 254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */, + CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */, B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, @@ -1950,6 +1997,7 @@ DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, + CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */, 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */, B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 636db362a2..a045f30b28 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -981,6 +981,17 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( _contentsScaleForDisplay = contentsScaleForDisplay; } +- (void)applyPendingViewState +{ + ASDisplayNodeAssertMainThread(); + if (self.layerBacked) { + [_pendingViewState applyToLayer:self.layer]; + } else { + [_pendingViewState applyToView:self.view]; + } + [_pendingViewState clearChanges]; +} + - (void)displayImmediately { ASDisplayNodeAssertMainThread(); @@ -2366,13 +2377,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // for the view/layer are still valid. ASDN::MutexLocker l(_propertyLock); - if (_flags.layerBacked) { - [_pendingViewState applyToLayer:_layer]; - } else { - [_pendingViewState applyToView:_view]; - } - - _pendingViewState = nil; + [self applyPendingViewState]; // TODO: move this into real pending state if (_flags.displaySuspended) { diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 70686f9819..147323a61b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -178,6 +178,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @property (nonatomic, assign) CGFloat contentsScaleForDisplay; +- (void)applyPendingViewState; + /** * // TODO: NOT YET IMPLEMENTED * diff --git a/AsyncDisplayKit/Private/ASPendingStateController.h b/AsyncDisplayKit/Private/ASPendingStateController.h new file mode 100644 index 0000000000..c67209030f --- /dev/null +++ b/AsyncDisplayKit/Private/ASPendingStateController.h @@ -0,0 +1,45 @@ +// +// ASPendingStateController.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@class ASDisplayNode; + +NS_ASSUME_NONNULL_BEGIN + +/** + A singleton that is responsible for applying changes to + UIView/CALayer properties of display nodes when they + have been set on background threads. + + This controller will enqueue run-loop events to flush changes + but if you need + */ +@interface ASPendingStateController : NSObject + ++ (ASPendingStateController *)sharedInstance; + +@property (nonatomic, readonly) BOOL hasChanges; + +/** + Flush all pending states for nodes now. Any UIView/CALayer properties + that have been set in the background will be applied to their + corresponding views/layers before this method returns. + + You must call this method on the main thread. + */ +- (void)flush; + +/** + Register this node as having pending state that needs + */ +- (void)registerNode:(ASDisplayNode *)node; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm new file mode 100644 index 0000000000..26141ae2e3 --- /dev/null +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -0,0 +1,99 @@ +// +// ASPendingStateController.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASPendingStateController.h" +#import "ASThread.h" +#import "ASWeakSet.h" +#import "ASDisplayNode.h" + +@interface ASPendingStateController() +{ + ASDN::Mutex _lock; + + struct ASPendingStateControllerFlags { + unsigned pendingFlush:1; + } _flags; +} + +@property (nonatomic, strong, readonly) ASWeakSet *dirtyNodes; +@end + +@implementation ASPendingStateController + +#pragma mark Lifecycle & Singleton + +- (instancetype)init +{ + self = [super init]; + if (self) { + _dirtyNodes = [ASWeakSet new]; + } + return self; +} + ++ (ASPendingStateController *)sharedInstance +{ + static dispatch_once_t onceToken; + static ASPendingStateController *controller; + dispatch_once(&onceToken, ^{ + controller = [ASPendingStateController new]; + }); + return controller; +} + +#pragma mark External API + +- (void)flush +{ + ASDisplayNodeAssertMainThread(); + [self flushNow]; +} + +- (void)registerNode:(ASDisplayNode *)node +{ + ASDN::MutexLocker l(_lock); + [_dirtyNodes addObject:node]; + + [self scheduleFlushIfNeeded]; +} + +#pragma mark Private Methods + +/** + This method is assumed to be called with the lock held. + */ +- (void)scheduleFlushIfNeeded +{ + if (_flags.pendingFlush) { + return; + } + + _flags.pendingFlush = YES; + [self performSelectorOnMainThread:@selector(flushNow) withObject:nil waitUntilDone:NO modes:@[ NSRunLoopCommonModes ]]; +} + +- (void)flushNow +{ + ASDN::MutexLocker l(_lock); + for (__unused ASDisplayNode *node in _dirtyNodes) { + // TODO: apply pending state. + } + [_dirtyNodes removeAllObjects]; + _flags.pendingFlush = NO; +} + +@end + +@implementation ASPendingStateController (Testing) + +- (BOOL)test_isFlushScheduled +{ + return _flags.pendingFlush; +} + +@end diff --git a/AsyncDisplayKit/Private/ASWeakSet.h b/AsyncDisplayKit/Private/ASWeakSet.h new file mode 100644 index 0000000000..8f6a6576ca --- /dev/null +++ b/AsyncDisplayKit/Private/ASWeakSet.h @@ -0,0 +1,40 @@ +// +// ASWeakSet.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASWeakSet<__covariant ObjectType> : NSObject + +/// Returns YES if the receiver is empty, NO otherwise. +@property (nonatomic, readonly, getter=isEmpty) BOOL empty; + +/// Returns YES if `object` is in the receiver, NO otherwise. +- (BOOL)containsObject:(ObjectType)object; + +/// Insets `object` into the set. +- (void)addObject:(ObjectType)object; + +/// Removes object from the set. +- (void)removeObject:(ObjectType)object; + +/// Removes all objects from the set. +- (void)removeAllObjects; + +/** + How many objects are contained in this set. + + NOTE: This method is O(N). Consider using the `empty` + property. + */ +@property (nonatomic, readonly) NSUInteger count; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m new file mode 100644 index 0000000000..1f41a9846b --- /dev/null +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -0,0 +1,68 @@ +// +// ASWeakSet.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASWeakSet.h" + +@interface ASWeakSet<__covariant ObjectType> () +@property (nonatomic, strong, readonly) NSMapTable *mapTable; +@end + +@implementation ASWeakSet + +- (instancetype)init +{ + self = [super init]; + if (self) { + _mapTable = [NSMapTable weakToStrongObjectsMapTable]; + } + return self; +} + +- (void)addObject:(id)object +{ + [_mapTable setObject:[NSNull null] forKey:object]; +} + +- (void)removeObject:(id)object +{ + [_mapTable removeObjectForKey:object]; +} + +- (void)removeAllObjects +{ + [_mapTable removeAllObjects]; +} + +- (BOOL)containsObject:(id)object +{ + return [_mapTable objectForKey:object] != nil; +} + +- (BOOL)isEmpty +{ + for (__unused id object in _mapTable) { + return NO; + } + return YES; +} + +- (NSUInteger)count +{ + NSInteger count = 0; + for (__unused id object in _mapTable) { + count += 1; + } + return count; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nonnull *)buffer count:(NSUInteger)len +{ + return [_mapTable countByEnumeratingWithState:state objects:buffer count:len]; +} + +@end diff --git a/AsyncDisplayKit/Private/_ASPendingState.h b/AsyncDisplayKit/Private/_ASPendingState.h index 4dd1146d24..531e81f169 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.h +++ b/AsyncDisplayKit/Private/_ASPendingState.h @@ -30,4 +30,8 @@ + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; + (_ASPendingState *)pendingViewStateFromView:(UIView *)view; +@property (nonatomic, readonly) BOOL hasChanges; + +- (void)clearChanges; + @end diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index b1ab0c5d39..85deb9c107 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -12,6 +12,57 @@ #import "_ASAsyncTransactionContainer.h" #import "ASAssert.h" +typedef struct { + // Properties + int needsDisplay:1; + int needsLayout:1; + + // Flags indicating that a given property should be applied to the view at creation + int setClipsToBounds:1; + int setOpaque:1; + int setNeedsDisplayOnBoundsChange:1; + int setAutoresizesSubviews:1; + int setAutoresizingMask:1; + int setFrame:1; + int setBounds:1; + int setBackgroundColor:1; + int setTintColor:1; + int setContents:1; + int setHidden:1; + int setAlpha:1; + int setCornerRadius:1; + int setContentMode:1; + int setNeedsDisplay:1; + int setAnchorPoint:1; + int setPosition:1; + int setZPosition:1; + int setContentsScale:1; + int setTransform:1; + int setSublayerTransform:1; + int setUserInteractionEnabled:1; + int setExclusiveTouch:1; + int setShadowColor:1; + int setShadowOpacity:1; + int setShadowOffset:1; + int setShadowRadius:1; + int setBorderWidth:1; + int setBorderColor:1; + int setAsyncTransactionContainer:1; + int setAllowsEdgeAntialiasing:1; + int setEdgeAntialiasingMask:1; + int setIsAccessibilityElement:1; + int setAccessibilityLabel:1; + int setAccessibilityHint:1; + int setAccessibilityValue:1; + int setAccessibilityTraits:1; + int setAccessibilityFrame:1; + int setAccessibilityLanguage:1; + int setAccessibilityElementsHidden:1; + int setAccessibilityViewIsModal:1; + int setShouldGroupAccessibilityChildren:1; + int setAccessibilityIdentifier:1; +} ASPendingStateFlags; + @implementation _ASPendingState { @package //Expose all ivars for ASDisplayNode to bypass getters for efficiency @@ -50,56 +101,7 @@ BOOL shouldGroupAccessibilityChildren; NSString *accessibilityIdentifier; - struct { - // Properties - int needsDisplay:1; - int needsLayout:1; - - // Flags indicating that a given property should be applied to the view at creation - int setClipsToBounds:1; - int setOpaque:1; - int setNeedsDisplayOnBoundsChange:1; - int setAutoresizesSubviews:1; - int setAutoresizingMask:1; - int setFrame:1; - int setBounds:1; - int setBackgroundColor:1; - int setTintColor:1; - int setContents:1; - int setHidden:1; - int setAlpha:1; - int setCornerRadius:1; - int setContentMode:1; - int setNeedsDisplay:1; - int setAnchorPoint:1; - int setPosition:1; - int setZPosition:1; - int setContentsScale:1; - int setTransform:1; - int setSublayerTransform:1; - int setUserInteractionEnabled:1; - int setExclusiveTouch:1; - int setShadowColor:1; - int setShadowOpacity:1; - int setShadowOffset:1; - int setShadowRadius:1; - int setBorderWidth:1; - int setBorderColor:1; - int setAsyncTransactionContainer:1; - int setAllowsEdgeAntialiasing:1; - int setEdgeAntialiasingMask:1; - int setIsAccessibilityElement:1; - int setAccessibilityLabel:1; - int setAccessibilityHint:1; - int setAccessibilityValue:1; - int setAccessibilityTraits:1; - int setAccessibilityFrame:1; - int setAccessibilityLanguage:1; - int setAccessibilityElementsHidden:1; - int setAccessibilityViewIsModal:1; - int setShouldGroupAccessibilityChildren:1; - int setAccessibilityIdentifier:1; - } _flags; + ASPendingStateFlags _flags; } @@ -199,12 +201,6 @@ static UIColor *defaultTintColor = nil; return self; } -- (CALayer *)layer -{ - ASDisplayNodeAssert(NO, @"One shouldn't call node.layer when the view isn't loaded, but we're returning nil to not crash if someone is still doing this"); - return nil; -} - - (void)setNeedsDisplay { _flags.needsDisplay = YES; @@ -560,91 +556,92 @@ static UIColor *defaultTintColor = nil; - (void)applyToLayer:(CALayer *)layer { - if (_flags.setAnchorPoint) + ASPendingStateFlags flags = _flags; + if (flags.setAnchorPoint) layer.anchorPoint = anchorPoint; - if (_flags.setPosition) + if (flags.setPosition) layer.position = position; - if (_flags.setZPosition) + if (flags.setZPosition) layer.zPosition = zPosition; - if (_flags.setBounds) + if (flags.setBounds) layer.bounds = bounds; - if (_flags.setContentsScale) + if (flags.setContentsScale) layer.contentsScale = contentsScale; - if (_flags.setTransform) + if (flags.setTransform) layer.transform = transform; - if (_flags.setSublayerTransform) + if (flags.setSublayerTransform) layer.sublayerTransform = sublayerTransform; - if (_flags.setContents) + if (flags.setContents) layer.contents = contents; - if (_flags.setClipsToBounds) + if (flags.setClipsToBounds) layer.masksToBounds = clipsToBounds; - if (_flags.setBackgroundColor) + if (flags.setBackgroundColor) layer.backgroundColor = backgroundColor; - if (_flags.setOpaque) + if (flags.setOpaque) layer.opaque = opaque; - if (_flags.setHidden) + if (flags.setHidden) layer.hidden = isHidden; - if (_flags.setAlpha) + if (flags.setAlpha) layer.opacity = alpha; - if (_flags.setCornerRadius) + if (flags.setCornerRadius) layer.cornerRadius = cornerRadius; - if (_flags.setContentMode) + if (flags.setContentMode) layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); - if (_flags.setShadowColor) + if (flags.setShadowColor) layer.shadowColor = shadowColor; - if (_flags.setShadowOpacity) + if (flags.setShadowOpacity) layer.shadowOpacity = shadowOpacity; - if (_flags.setShadowOffset) + if (flags.setShadowOffset) layer.shadowOffset = shadowOffset; - if (_flags.setShadowRadius) + if (flags.setShadowRadius) layer.shadowRadius = shadowRadius; - if (_flags.setBorderWidth) + if (flags.setBorderWidth) layer.borderWidth = borderWidth; - if (_flags.setBorderColor) + if (flags.setBorderColor) layer.borderColor = borderColor; - if (_flags.setNeedsDisplayOnBoundsChange) + if (flags.setNeedsDisplayOnBoundsChange) layer.needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; - if (_flags.setAllowsEdgeAntialiasing) + if (flags.setAllowsEdgeAntialiasing) layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing; - if (_flags.setEdgeAntialiasingMask) + if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (_flags.needsDisplay) + if (flags.needsDisplay) [layer setNeedsDisplay]; - if (_flags.needsLayout) + if (flags.needsLayout) [layer setNeedsLayout]; - if (_flags.setAsyncTransactionContainer) + if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; - if (_flags.setOpaque) + if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - if (_flags.setFrame) + if (flags.setFrame) ASDisplayNodeAssert(NO, @"Frame property should only be used for synchronously wrapped nodes. See setFrame: in ASDisplayNode+UIViewBridge"); } @@ -660,143 +657,144 @@ static UIColor *defaultTintColor = nil; CALayer *layer = view.layer; - if (_flags.setAnchorPoint) + ASPendingStateFlags flags = _flags; + if (flags.setAnchorPoint) layer.anchorPoint = anchorPoint; - if (_flags.setPosition) + if (flags.setPosition) layer.position = position; - if (_flags.setZPosition) + if (flags.setZPosition) layer.zPosition = zPosition; // This should only be used for synchronous views wrapped by nodes. - if (_flags.setFrame && !(_flags.setBounds && _flags.setPosition)) { + if (flags.setFrame && !(flags.setBounds && flags.setPosition)) { view.frame = frame; } - if (_flags.setBounds) + if (flags.setBounds) view.bounds = bounds; - if (_flags.setContentsScale) + if (flags.setContentsScale) layer.contentsScale = contentsScale; - if (_flags.setTransform) + if (flags.setTransform) layer.transform = transform; - if (_flags.setSublayerTransform) + if (flags.setSublayerTransform) layer.sublayerTransform = sublayerTransform; - if (_flags.setContents) + if (flags.setContents) layer.contents = contents; - if (_flags.setClipsToBounds) + if (flags.setClipsToBounds) view.clipsToBounds = clipsToBounds; - if (_flags.setBackgroundColor) + if (flags.setBackgroundColor) layer.backgroundColor = backgroundColor; - if (_flags.setTintColor) + if (flags.setTintColor) view.tintColor = self.tintColor; - if (_flags.setOpaque) + if (flags.setOpaque) view.layer.opaque = opaque; - if (_flags.setHidden) + if (flags.setHidden) view.hidden = isHidden; - if (_flags.setAlpha) + if (flags.setAlpha) view.alpha = alpha; - if (_flags.setCornerRadius) + if (flags.setCornerRadius) layer.cornerRadius = cornerRadius; - if (_flags.setContentMode) + if (flags.setContentMode) view.contentMode = contentMode; - if (_flags.setUserInteractionEnabled) + if (flags.setUserInteractionEnabled) view.userInteractionEnabled = userInteractionEnabled; #if TARGET_OS_IOS - if (_flags.setExclusiveTouch) + if (flags.setExclusiveTouch) view.exclusiveTouch = exclusiveTouch; #endif - if (_flags.setShadowColor) + if (flags.setShadowColor) layer.shadowColor = shadowColor; - if (_flags.setShadowOpacity) + if (flags.setShadowOpacity) layer.shadowOpacity = shadowOpacity; - if (_flags.setShadowOffset) + if (flags.setShadowOffset) layer.shadowOffset = shadowOffset; - if (_flags.setShadowRadius) + if (flags.setShadowRadius) layer.shadowRadius = shadowRadius; - if (_flags.setBorderWidth) + if (flags.setBorderWidth) layer.borderWidth = borderWidth; - if (_flags.setBorderColor) + if (flags.setBorderColor) layer.borderColor = borderColor; - if (_flags.setAutoresizingMask) + if (flags.setAutoresizingMask) view.autoresizingMask = autoresizingMask; - if (_flags.setAutoresizesSubviews) + if (flags.setAutoresizesSubviews) view.autoresizesSubviews = autoresizesSubviews; - if (_flags.setNeedsDisplayOnBoundsChange) + if (flags.setNeedsDisplayOnBoundsChange) layer.needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; - if (_flags.setAllowsEdgeAntialiasing) + if (flags.setAllowsEdgeAntialiasing) layer.allowsEdgeAntialiasing = allowsEdgeAntialiasing; - if (_flags.setEdgeAntialiasingMask) + if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (_flags.needsDisplay) + if (flags.needsDisplay) [view setNeedsDisplay]; - if (_flags.needsLayout) + if (flags.needsLayout) [view setNeedsLayout]; - if (_flags.setAsyncTransactionContainer) + if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; - if (_flags.setOpaque) + if (flags.setOpaque) ASDisplayNodeAssert(view.layer.opaque == opaque, @"Didn't set opaque as desired"); - if (_flags.setIsAccessibilityElement) + if (flags.setIsAccessibilityElement) view.isAccessibilityElement = isAccessibilityElement; - if (_flags.setAccessibilityLabel) + if (flags.setAccessibilityLabel) view.accessibilityLabel = accessibilityLabel; - if (_flags.setAccessibilityHint) + if (flags.setAccessibilityHint) view.accessibilityHint = accessibilityHint; - if (_flags.setAccessibilityValue) + if (flags.setAccessibilityValue) view.accessibilityValue = accessibilityValue; - if (_flags.setAccessibilityTraits) + if (flags.setAccessibilityTraits) view.accessibilityTraits = accessibilityTraits; - if (_flags.setAccessibilityFrame) + if (flags.setAccessibilityFrame) view.accessibilityFrame = accessibilityFrame; - if (_flags.setAccessibilityLanguage) + if (flags.setAccessibilityLanguage) view.accessibilityLanguage = accessibilityLanguage; - if (_flags.setAccessibilityElementsHidden) + if (flags.setAccessibilityElementsHidden) view.accessibilityElementsHidden = accessibilityElementsHidden; - if (_flags.setAccessibilityViewIsModal) + if (flags.setAccessibilityViewIsModal) view.accessibilityViewIsModal = accessibilityViewIsModal; - if (_flags.setShouldGroupAccessibilityChildren) + if (flags.setShouldGroupAccessibilityChildren) view.shouldGroupAccessibilityChildren = shouldGroupAccessibilityChildren; - if (_flags.setAccessibilityIdentifier) + if (flags.setAccessibilityIdentifier) view.accessibilityIdentifier = accessibilityIdentifier; } @@ -806,81 +804,31 @@ static UIColor *defaultTintColor = nil; if (!layer) { return nil; } - _ASPendingState *pendingState = [[_ASPendingState alloc] init]; - pendingState.anchorPoint = layer.anchorPoint; - (pendingState->_flags).setAnchorPoint = YES; - pendingState.position = layer.position; - (pendingState->_flags).setPosition = YES; - pendingState.zPosition = layer.zPosition; - (pendingState->_flags).setZPosition = YES; - pendingState.bounds = layer.bounds; - (pendingState->_flags).setBounds = YES; - pendingState.contentsScale = layer.contentsScale; - (pendingState->_flags).setContentsScale = YES; - pendingState.transform = layer.transform; - (pendingState->_flags).setTransform = YES; - pendingState.sublayerTransform = layer.sublayerTransform; - (pendingState->_flags).setSublayerTransform = YES; - pendingState.contents = layer.contents; - (pendingState->_flags).setContents = YES; - pendingState.clipsToBounds = layer.masksToBounds; - (pendingState->_flags).setClipsToBounds = YES; - pendingState.backgroundColor = layer.backgroundColor; - (pendingState->_flags).setBackgroundColor = YES; - pendingState.opaque = layer.opaque; - (pendingState->_flags).setOpaque = YES; - pendingState.hidden = layer.hidden; - (pendingState->_flags).setHidden = YES; - pendingState.alpha = layer.opacity; - (pendingState->_flags).setAlpha = YES; - pendingState.cornerRadius = layer.cornerRadius; - (pendingState->_flags).setCornerRadius = YES; - pendingState.contentMode = ASDisplayNodeUIContentModeFromCAContentsGravity(layer.contentsGravity); - (pendingState->_flags).setContentMode = YES; - pendingState.shadowColor = layer.shadowColor; - (pendingState->_flags).setShadowColor = YES; - pendingState.shadowOpacity = layer.shadowOpacity; - (pendingState->_flags).setShadowOpacity = YES; - pendingState.shadowOffset = layer.shadowOffset; - (pendingState->_flags).setShadowOffset = YES; - pendingState.shadowRadius = layer.shadowRadius; - (pendingState->_flags).setShadowRadius = YES; - pendingState.borderWidth = layer.borderWidth; - (pendingState->_flags).setBorderWidth = YES; - pendingState.borderColor = layer.borderColor; - (pendingState->_flags).setBorderColor = YES; - pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange; - (pendingState->_flags).setNeedsDisplayOnBoundsChange = YES; - pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; - (pendingState->_flags).setAllowsEdgeAntialiasing = YES; - pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - (pendingState->_flags).setEdgeAntialiasingMask = YES; - return pendingState; } @@ -890,134 +838,110 @@ static UIColor *defaultTintColor = nil; if (!view) { return nil; } - _ASPendingState *pendingState = [[_ASPendingState alloc] init]; - + CALayer *layer = view.layer; - pendingState.anchorPoint = layer.anchorPoint; - (pendingState->_flags).setAnchorPoint = YES; - pendingState.position = layer.position; - (pendingState->_flags).setPosition = YES; - pendingState.zPosition = layer.zPosition; - (pendingState->_flags).setZPosition = YES; - pendingState.bounds = view.bounds; - (pendingState->_flags).setBounds = YES; - pendingState.contentsScale = layer.contentsScale; - (pendingState->_flags).setContentsScale = YES; - pendingState.transform = layer.transform; - (pendingState->_flags).setTransform = YES; - pendingState.sublayerTransform = layer.sublayerTransform; - (pendingState->_flags).setSublayerTransform = YES; - pendingState.contents = layer.contents; - (pendingState->_flags).setContents = YES; - pendingState.clipsToBounds = view.clipsToBounds; - (pendingState->_flags).setClipsToBounds = YES; - pendingState.backgroundColor = layer.backgroundColor; - (pendingState->_flags).setBackgroundColor = YES; - pendingState.tintColor = view.tintColor; - (pendingState->_flags).setTintColor = YES; - pendingState.opaque = layer.opaque; - (pendingState->_flags).setOpaque = YES; - pendingState.hidden = view.hidden; - (pendingState->_flags).setHidden = YES; - pendingState.alpha = view.alpha; - (pendingState->_flags).setAlpha = YES; - pendingState.cornerRadius = layer.cornerRadius; - (pendingState->_flags).setCornerRadius = YES; - pendingState.contentMode = view.contentMode; - (pendingState->_flags).setContentMode = YES; - pendingState.userInteractionEnabled = view.userInteractionEnabled; - (pendingState->_flags).setUserInteractionEnabled = YES; #if TARGET_OS_IOS pendingState.exclusiveTouch = view.exclusiveTouch; - (pendingState->_flags).setExclusiveTouch = YES; #endif pendingState.shadowColor = layer.shadowColor; - (pendingState->_flags).setShadowColor = YES; - pendingState.shadowOpacity = layer.shadowOpacity; - (pendingState->_flags).setShadowOpacity = YES; - pendingState.shadowOffset = layer.shadowOffset; - (pendingState->_flags).setShadowOffset = YES; - pendingState.shadowRadius = layer.shadowRadius; - (pendingState->_flags).setShadowRadius = YES; - pendingState.borderWidth = layer.borderWidth; - (pendingState->_flags).setBorderWidth = YES; - pendingState.borderColor = layer.borderColor; - (pendingState->_flags).setBorderColor = YES; - pendingState.autoresizingMask = view.autoresizingMask; - (pendingState->_flags).setAutoresizingMask = YES; - pendingState.autoresizesSubviews = view.autoresizesSubviews; - (pendingState->_flags).setAutoresizesSubviews = YES; - pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange; - (pendingState->_flags).setNeedsDisplayOnBoundsChange = YES; - pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; - (pendingState->_flags).setAllowsEdgeAntialiasing = YES; - pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - (pendingState->_flags).setEdgeAntialiasingMask = YES; - pendingState.isAccessibilityElement = view.isAccessibilityElement; - (pendingState->_flags).setIsAccessibilityElement = YES; - pendingState.accessibilityLabel = view.accessibilityLabel; - (pendingState->_flags).setAccessibilityLabel = YES; - pendingState.accessibilityHint = view.accessibilityHint; - (pendingState->_flags).setAccessibilityHint = YES; - pendingState.accessibilityValue = view.accessibilityValue; - (pendingState->_flags).setAccessibilityValue = YES; - pendingState.accessibilityTraits = view.accessibilityTraits; - (pendingState->_flags).setAccessibilityTraits = YES; - pendingState.accessibilityFrame = view.accessibilityFrame; - (pendingState->_flags).setAccessibilityFrame = YES; - pendingState.accessibilityLanguage = view.accessibilityLanguage; - (pendingState->_flags).setAccessibilityLanguage = YES; - pendingState.accessibilityElementsHidden = view.accessibilityElementsHidden; - (pendingState->_flags).setAccessibilityElementsHidden = YES; - pendingState.accessibilityViewIsModal = view.accessibilityViewIsModal; - (pendingState->_flags).setAccessibilityViewIsModal = YES; - pendingState.shouldGroupAccessibilityChildren = view.shouldGroupAccessibilityChildren; - (pendingState->_flags).setShouldGroupAccessibilityChildren = YES; - pendingState.accessibilityIdentifier = view.accessibilityIdentifier; - (pendingState->_flags).setAccessibilityIdentifier = YES; - return pendingState; } +- (void)clearChanges +{ + _flags = (ASPendingStateFlags){ 0 }; +} + +- (BOOL)hasChanges +{ + ASPendingStateFlags flags = _flags; + + return (flags.setAnchorPoint + || flags.setPosition + || flags.setZPosition + || flags.setFrame + || flags.setBounds + || flags.setPosition + || flags.setContentsScale + || flags.setTransform + || flags.setSublayerTransform + || flags.setContents + || flags.setClipsToBounds + || flags.setBackgroundColor + || flags.setTintColor + || flags.setHidden + || flags.setAlpha + || flags.setCornerRadius + || flags.setContentMode + || flags.setUserInteractionEnabled + || flags.setExclusiveTouch + || flags.setShadowOpacity + || flags.setShadowOffset + || flags.setShadowRadius + || flags.setShadowColor + || flags.setBorderWidth + || flags.setBorderColor + || flags.setAutoresizingMask + || flags.setAutoresizesSubviews + || flags.setNeedsDisplayOnBoundsChange + || flags.setAllowsEdgeAntialiasing + || flags.setEdgeAntialiasingMask + || flags.needsDisplay + || flags.needsLayout + || flags.setAsyncTransactionContainer + || flags.setOpaque + || flags.setIsAccessibilityElement + || flags.setAccessibilityLabel + || flags.setAccessibilityHint + || flags.setAccessibilityValue + || flags.setAccessibilityTraits + || flags.setAccessibilityFrame + || flags.setAccessibilityLanguage + || flags.setAccessibilityElementsHidden + || flags.setAccessibilityViewIsModal + || flags.setShouldGroupAccessibilityChildren + || flags.setAccessibilityIdentifier); +} + - (void)dealloc { CGColorRelease(backgroundColor); diff --git a/AsyncDisplayKitTests/ASPendingStateControllerTests.m b/AsyncDisplayKitTests/ASPendingStateControllerTests.m new file mode 100644 index 0000000000..ef1f583242 --- /dev/null +++ b/AsyncDisplayKitTests/ASPendingStateControllerTests.m @@ -0,0 +1,40 @@ +// +// ASPendingStateControllerTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "ASPendingStateController.h" +#import "ASDisplayNode.h" + +@interface ASPendingStateController (Testing) +- (BOOL)test_isFlushScheduled; +@end + +@interface ASPendingStateControllerTests : XCTestCase + +@end + +@implementation ASPendingStateControllerTests + +- (void)testTheresASharedInstance +{ + XCTAssertNotNil([ASPendingStateController sharedInstance]); +} + +- (void)testThatRegisteringANodeCausesAtFlushAtRunLoopEnd +{ + ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; + ASDisplayNode *node = [ASDisplayNode new]; + XCTAssertFalse(ctrl.test_isFlushScheduled); + [ctrl registerNode:node]; + XCTAssertTrue(ctrl.test_isFlushScheduled); + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:1]; + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; + XCTAssertFalse(ctrl.test_isFlushScheduled); +} + +@end diff --git a/AsyncDisplayKitTests/ASWeakSetTests.m b/AsyncDisplayKitTests/ASWeakSetTests.m new file mode 100644 index 0000000000..09779f3267 --- /dev/null +++ b/AsyncDisplayKitTests/ASWeakSetTests.m @@ -0,0 +1,134 @@ +// +// ASWeakSetTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "ASWeakSet.h" + +@interface ASWeakSetTests : XCTestCase + +@end + +@implementation ASWeakSetTests + +- (void)testAddingACoupleRetainedObjects +{ + ASWeakSet *weakSet = [ASWeakSet new]; + NSString *hello = @"hello"; + NSString *world = @"hello"; + [weakSet addObject:hello]; + [weakSet addObject:world]; + XCTAssert([weakSet containsObject:hello]); + XCTAssert([weakSet containsObject:world]); + XCTAssert(![weakSet containsObject:@"apple"]); +} + +- (void)testThatCountIncorporatesDeallocatedObjects +{ + ASWeakSet *weakSet = [ASWeakSet new]; + XCTAssertEqual(weakSet.count, 0); + NSObject *a = [NSObject new]; + NSObject *b = [NSObject new]; + [weakSet addObject:a]; + [weakSet addObject:b]; + XCTAssertEqual(weakSet.count, 2); + + @autoreleasepool { + NSObject *doomedObject = [NSObject new]; + [weakSet addObject:doomedObject]; + XCTAssertEqual(weakSet.count, 3); + } + + XCTAssertEqual(weakSet.count, 2); +} + +- (void)testThatIsEmptyIncorporatesDeallocatedObjects +{ + ASWeakSet *weakSet = [ASWeakSet new]; + XCTAssertTrue(weakSet.isEmpty); + @autoreleasepool { + NSObject *doomedObject = [NSObject new]; + [weakSet addObject:doomedObject]; + XCTAssertFalse(weakSet.isEmpty); + } + XCTAssertTrue(weakSet.isEmpty); +} + +- (void)testThatContainsObjectWorks +{ + ASWeakSet *weakSet = [ASWeakSet new]; + NSObject *a = [NSObject new]; + NSObject *b = [NSObject new]; + [weakSet addObject:a]; + XCTAssertTrue([weakSet containsObject:a]); + XCTAssertFalse([weakSet containsObject:b]); +} + +- (void)testThatRemoveObjectWorks +{ + ASWeakSet *weakSet = [ASWeakSet new]; + NSObject *a = [NSObject new]; + NSObject *b = [NSObject new]; + [weakSet addObject:a]; + [weakSet addObject:b]; + XCTAssertTrue([weakSet containsObject:a]); + XCTAssertTrue([weakSet containsObject:b]); + XCTAssertEqual(weakSet.count, 2); + + [weakSet removeObject:b]; + XCTAssertTrue([weakSet containsObject:a]); + XCTAssertFalse([weakSet containsObject:b]); + XCTAssertEqual(weakSet.count, 1); +} + +- (void)testThatFastEnumerationWorks +{ + ASWeakSet *weakSet = [ASWeakSet new]; + NSObject *a = [NSObject new]; + NSObject *b = [NSObject new]; + [weakSet addObject:a]; + [weakSet addObject:b]; + + @autoreleasepool { + NSObject *doomedObject = [NSObject new]; + [weakSet addObject:doomedObject]; + XCTAssertEqual(weakSet.count, 3); + } + + NSInteger i = 0; + NSMutableSet *awaitingObjects = [NSMutableSet setWithObjects:a, b, nil]; + for (NSObject *object in weakSet) { + XCTAssertTrue([awaitingObjects containsObject:object]); + [awaitingObjects removeObject:object]; + i += 1; + } + + XCTAssertEqual(i, 2); +} + +- (void)testThatRemoveAllObjectsWorks +{ + ASWeakSet *weakSet = [ASWeakSet new]; + NSObject *a = [NSObject new]; + NSObject *b = [NSObject new]; + [weakSet addObject:a]; + [weakSet addObject:b]; + XCTAssertEqual(weakSet.count, 2); + + [weakSet removeAllObjects]; + + XCTAssertEqual(weakSet.count, 0); + + NSInteger i = 0; + for (__unused NSObject *object in weakSet) { + i += 1; + } + + XCTAssertEqual(i, 0); +} + +@end