From 580b7bdc7889a51243933f9dcfe3764803b17cc0 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 28 Jan 2016 17:40:21 -0800 Subject: [PATCH 001/224] Move the call to willDisplayNodeContentWithRenderingContext to before background fill in image node --- AsyncDisplayKit/ASImageNode.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 9f67f03d2e..c3d5d330a5 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -238,17 +238,17 @@ // will do its rounding on pixel instead of point boundaries UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context && preContextBlock) { + preContextBlock(context); + } + // if view is opaque, fill the context with background color if (isOpaque && backgroundColor) { [backgroundColor setFill]; UIRectFill({ .size = backingSize }); } - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context && preContextBlock) { - preContextBlock(context); - } - // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier, From af37a48421567531b77f7dc86370ab04e483c64d Mon Sep 17 00:00:00 2001 From: Luke Parham Date: Thu, 28 Jan 2016 23:34:16 -0800 Subject: [PATCH 002/224] removed unnecessary clear color and improved comment --- AsyncDisplayKit/Private/ASDefaultPlayButton.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.m b/AsyncDisplayKit/Private/ASDefaultPlayButton.m index 371c598faa..7cf9f6d49f 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlayButton.m +++ b/AsyncDisplayKit/Private/ASDefaultPlayButton.m @@ -27,24 +27,18 @@ CGRect buttonBounds = CGRectMake(originX, bounds.size.height/4, bounds.size.width/2, bounds.size.height/2); CGFloat widthHeight = buttonBounds.size.width; + //When the video isn't a square, the lower bound should be used to figure out the circle size if (bounds.size.width < bounds.size.height) { - //then use the width to determine the rect size then calculate the origin x y widthHeight = bounds.size.width/2; originX = (bounds.size.width - widthHeight)/2; buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); } if (bounds.size.width > bounds.size.height) { - //use the height widthHeight = bounds.size.height/2; originX = (bounds.size.width - widthHeight)/2; buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); } - - if (!isRasterizing) { - [[UIColor clearColor] set]; - UIRectFill(bounds); - } - + CGContextRef context = UIGraphicsGetCurrentContext(); // Circle Drawing From d0edbe809af8ac39d5aafb539b3b0205b4213303 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 29 Jan 2016 09:32:40 -0500 Subject: [PATCH 003/224] ensure that the truncater is initialized before the context is in _calculateSize --- AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index aa6b94d525..05996c4de3 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -103,8 +103,6 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() constrainedSize:shadowConstrainedSize layoutManagerFactory:attributes.layoutManagerFactory layoutManagerDelegate:attributes.layoutManagerDelegate]; - - [self truncater]; } return _context; } @@ -138,6 +136,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (void)_calculateSize { + [self truncater]; if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) { [[self fontSizeAdjuster] adjustFontSize]; } From 2d1499ab4f623e796b4fc09af904d35e15e40105 Mon Sep 17 00:00:00 2001 From: Luke Parham Date: Fri, 29 Jan 2016 09:27:48 -0800 Subject: [PATCH 004/224] added muting property and delegate callback to override video tapping --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 +++++++++ AsyncDisplayKit/ASVideoNode.h | 3 ++ AsyncDisplayKit/ASVideoNode.mm | 36 ++++++++++++++++--- AsyncDisplayKit/Private/ASDefaultPlayButton.m | 1 - examples/Videos/Sample/ViewController.m | 14 ++++++-- 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index fe19c59286..b67b61efc3 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1616,6 +1616,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + FB42E06CF915B60406431170 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1745,6 +1746,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + FB42E06CF915B60406431170 /* 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 */ diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 7df171d005..515dc20f4b 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -24,6 +24,8 @@ @property (nonatomic, assign, readwrite) BOOL shouldAutoplay; @property (nonatomic, assign, readwrite) BOOL shouldAutorepeat; +@property (nonatomic, assign, readwrite) BOOL muted; + @property (atomic) NSString *gravity; @property (atomic) ASButtonNode *playButton; @@ -39,5 +41,6 @@ @protocol ASVideoNodeDelegate @optional - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode; +- (void)videoNodeWasTapped:(ASVideoNode *)videoNode; @end diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index f6f717f452..6ba344d35a 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -19,6 +19,8 @@ BOOL _shouldAutorepeat; BOOL _shouldAutoplay; + + BOOL _muted; AVAsset *_asset; @@ -31,6 +33,7 @@ ASDisplayNode *_playerNode; ASDisplayNode *_spinner; NSString *_gravity; + dispatch_queue_t _previewQueue; } @@ -137,6 +140,7 @@ AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; if (!_player) { _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; + _player.muted = _muted; } playerLayer.player = _player; playerLayer.videoGravity = [self gravity]; @@ -176,10 +180,14 @@ - (void)tapped { - if (_shouldBePlaying) { - [self pause]; + if (self.delegate && [self.delegate respondsToSelector:@selector(videoNodeWasTapped:)]) { + [self.delegate videoNodeWasTapped:self]; } else { - [self play]; + if (_shouldBePlaying) { + [self pause]; + } else { + [self play]; + } } } @@ -209,11 +217,11 @@ [_player replaceCurrentItemWithPlayerItem:_currentItem]; } else { _player = [[AVPlayer alloc] initWithPlayerItem:_currentItem]; + _player.muted = _muted; } } } - - (void)clearFetchedData { [super clearFetchedData]; @@ -231,11 +239,14 @@ if (_shouldAutoplay && _playerNode.isNodeLoaded) { [self play]; + } else if (_shouldAutoplay) { + _shouldBePlaying = YES; } if (isVisible) { if (_playerNode.isNodeLoaded) { if (!_player) { _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; + _player.muted = _muted; } ((AVPlayerLayer *)_playerNode.layer).player = _player; } @@ -256,7 +267,7 @@ [self addSubnode:playButton]; - [_playButton addTarget:self action:@selector(play) forControlEvents:ASControlNodeEventTouchUpInside]; + [_playButton addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; } - (ASButtonNode *)playButton @@ -310,6 +321,20 @@ return _gravity; } +- (BOOL)muted +{ + ASDN::MutexLocker l(_lock); + + return _muted; +} + +- (void)setMuted:(BOOL)muted +{ + ASDN::MutexLocker l(_lock); + + _muted = muted; +} + #pragma mark - Video Playback - (void)play @@ -330,6 +355,7 @@ AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; if (!_player) { _player = [AVPlayer playerWithPlayerItem:[[AVPlayerItem alloc] initWithAsset:_asset]]; + _player.muted = _muted; } playerLayer.player = _player; playerLayer.videoGravity = [self gravity]; diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.m b/AsyncDisplayKit/Private/ASDefaultPlayButton.m index 7cf9f6d49f..364b86a6e5 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlayButton.m +++ b/AsyncDisplayKit/Private/ASDefaultPlayButton.m @@ -44,7 +44,6 @@ // Circle Drawing UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect: buttonBounds]; [[UIColor colorWithWhite:0.0 alpha:0.5] setFill]; - [ovalPath stroke]; [ovalPath fill]; // Triangle Drawing diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index d36c928ead..5c4c0488ef 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -43,7 +43,6 @@ videoNode.backgroundColor = [UIColor lightGrayColor]; -// videoNode.playButton = [self playButton]; return videoNode; } @@ -61,7 +60,8 @@ nicCageVideo.backgroundColor = [UIColor lightGrayColor]; nicCageVideo.shouldAutorepeat = YES; -// nicCageVideo.playButton = [self playButton]; + nicCageVideo.shouldAutoplay = YES; + nicCageVideo.muted = YES; return nicCageVideo; } @@ -79,7 +79,6 @@ simonVideo.backgroundColor = [UIColor lightGrayColor]; simonVideo.shouldAutorepeat = YES; -// simonVideo.playButton = [self playButton]; simonVideo.shouldAutoplay = YES; return simonVideo; @@ -99,6 +98,15 @@ return playButton; } +- (void)videoNodeWasTapped:(ASVideoNode *)videoNode +{ + if (videoNode.player.muted == YES) { + videoNode.player.muted = NO; + } else { + videoNode.player.muted = YES; + } +} + - (BOOL)prefersStatusBarHidden { return YES; From 35fb3d2ae66ba3ca1e2ddafd4857ddf66c19fd85 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 29 Jan 2016 14:45:18 -0800 Subject: [PATCH 005/224] Allow images returned by placeholderImage to be stretchable --- AsyncDisplayKit/ASDisplayNode.mm | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index aa54b85671..cd0cb2fb96 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -18,6 +18,7 @@ #import "_ASPendingState.h" #import "_ASDisplayView.h" #import "_ASScopeTimer.h" +#import "_ASCoreAnimationExtras.h" #import "ASDisplayNodeExtras.h" #import "ASEqualityHelpers.h" @@ -615,7 +616,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } if (_placeholderLayer) { - _placeholderLayer.contents = (id)_placeholderImage.CGImage; + [self setupPlaceholderLayerContents]; } } @@ -2011,13 +2012,24 @@ static BOOL ShouldUseNewRenderingRange = YES; if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) { [CATransaction begin]; [CATransaction setDisableActions:YES]; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; + [self setupPlaceholderLayerContents]; _placeholderLayer.opacity = 1.0; [CATransaction commit]; [self.layer addSublayer:_placeholderLayer]; } } +- (void)setupPlaceholderLayerContents +{ + BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); + if (stretchable) { + ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); + } else { + _placeholderLayer.contentsScale = self.contentsScale; + _placeholderLayer.contents = (id)_placeholderImage.CGImage; + } +} + - (void)displayDidFinish { [self _pendingNodeDidDisplay:self]; From 2313b3240619972e2808f2ae43ee7dc34d3bb0e4 Mon Sep 17 00:00:00 2001 From: Luke Parham Date: Fri, 29 Jan 2016 16:31:54 -0800 Subject: [PATCH 006/224] removed comment --- AsyncDisplayKitTests/ASVideoNodeTests.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 50b06c1aee..465636f21e 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -73,8 +73,6 @@ XCTAssertEqualObjects(item, secondItem); } -//Touch Handling - - (void)testSpinnerDefaultsToNil { XCTAssertNil(_videoNode.spinner); From b889d81de86fca775686a873f7c132aca894c24d Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 31 Jan 2016 00:53:28 -0800 Subject: [PATCH 007/224] Don't pre-render cell nodes of collection view when it is not visible or not scrolling --- AsyncDisplayKit/ASCollectionView.h | 5 +++ AsyncDisplayKit/ASCollectionView.mm | 32 +++++++++++-------- AsyncDisplayKit/ASDisplayNode.mm | 1 + .../ASCollectionViewLayoutController.mm | 16 ++++++++++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 418d22c2d3..b1ff87f251 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -90,6 +90,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, weak) id layoutInspector; +/** + * The ASInterfaceState of this collection view. + */ +@property (nonatomic, readonly) ASInterfaceState interfaceState; + /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. * The asyncDataSource must be updated to reflect the changes before the update block completes. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 523d8f9f8f..d177b49173 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -17,7 +17,6 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" -#import "ASRangeController.h" #import "UICollectionViewLayout+ASConvenience.h" #import "_ASDisplayLayer.h" @@ -243,6 +242,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return _flowLayoutInspector; } +- (ASInterfaceState)interfaceState +{ + ASCollectionNode *collectionNode = self.collectionNode; + if (collectionNode) { + return collectionNode.interfaceState; + } else { + // Until we can always create an associated ASCollectionNode without a retain cycle, + // we might be on our own to try to guess if we're visible. The node normally + // handles this even if it is the root / directly added to the view hierarchy. + return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); + } +} + #pragma mark - #pragma mark Overrides. @@ -535,16 +547,16 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASScrollDirection scrollableDirections = [self scrollableDirections]; if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x >= 0) { + if (scrollVelocity.x > 0) { direction |= ASScrollDirectionRight; - } else { + } else if (scrollVelocity.x < 0) { direction |= ASScrollDirectionLeft; } } if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y >= 0) { + if (scrollVelocity.y > 0) { direction |= ASScrollDirectionDown; - } else { + } else if (scrollVelocity.y < 0) { direction |= ASScrollDirectionUp; } } @@ -829,15 +841,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { - ASCollectionNode *collectionNode = self.collectionNode; - if (collectionNode) { - return self.collectionNode.interfaceState; - } else { - // Until we can always create an associated ASCollectionNode without a retain cycle, - // we might be on our own to try to guess if we're visible. The node normally - // handles this even if it is the root / directly added to the view hierarchy. - return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); - } + return self.interfaceState; } - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index aa54b85671..63ba58d659 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1585,6 +1585,7 @@ static BOOL ShouldUseNewRenderingRange = YES; { return ShouldUseNewRenderingRange; } + + (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange { ShouldUseNewRenderingRange = shouldUseNewRenderingRange; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index c959e86434..f62e54f1cc 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -14,6 +14,7 @@ #import "ASCollectionView.h" #import "CGRect+ASConvenience.h" #import "UICollectionViewLayout+ASConvenience.h" +#import "ASDisplayNodeExtras.h" struct ASRangeGeometry { CGRect rangeBounds; @@ -164,4 +165,19 @@ typedef struct ASRangeGeometry ASRangeGeometry; return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, _scrollableDirections, scrollDirection); } +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + BOOL isVisible = ASInterfaceStateIncludesVisible(_collectionView.interfaceState); + BOOL isScrolling = (_collectionView.scrollDirection != ASScrollDirectionNone); + // When the collecion view is not visible or not scrolling, make display range only as big as visible range. + // This reduce early creation of views and layers. + if (!isVisible || !isScrolling) { + if (rangeType == ASLayoutRangeTypeDisplay) { + return [super tuningParametersForRangeType:ASLayoutRangeTypeVisible]; + } + } + + return [super tuningParametersForRangeType:rangeType]; +} + @end From dfefcb8a89cab98216ef1a0f2892f6d5e00a4f3f Mon Sep 17 00:00:00 2001 From: mb Date: Mon, 1 Feb 2016 15:35:24 +0100 Subject: [PATCH 008/224] disabled GCC_INSTRUMENT_PROGRAM_FLOW_ARCS for Release build of iOS Framework to fix error output in projects using that Framework --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index fe19c59286..0091e38901 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -2285,7 +2285,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; INFOPLIST_FILE = "$(SRCROOT)/AsyncDisplayKit-iOS/Info.plist"; From 836ab9c17f0af09865424c46faebc7d93fcf2f29 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 10:47:39 -0800 Subject: [PATCH 009/224] Migrate Swift example to Framework --- examples/Swift/Podfile | 3 ++ .../Swift/Sample.xcodeproj/project.pbxproj | 28 +++++++++++++------ .../Sample/AsyncDisplayKit-Bridging-Header.h | 12 -------- examples/Swift/Sample/ViewController.swift | 1 + 4 files changed, 24 insertions(+), 20 deletions(-) delete mode 100644 examples/Swift/Sample/AsyncDisplayKit-Bridging-Header.h diff --git a/examples/Swift/Podfile b/examples/Swift/Podfile index 6c012e3c04..d76ea6718b 100644 --- a/examples/Swift/Podfile +++ b/examples/Swift/Podfile @@ -1,3 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' + +use_frameworks! + pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index 6675b3b34c..ac8d78cedf 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -10,9 +10,9 @@ 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7319D22E19004363C2 /* AppDelegate.swift */; }; 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7519D22E19004363C2 /* ViewController.swift */; }; 05DDD8DB19D2336300013C30 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */; }; - 4690009EF79C47BBA8FDBAD4 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2ACC614D420B4E90B7EE3BCE /* libPods.a */; }; 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053D919EE266A00E385DE /* Default-667h@2x.png */; }; 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */; }; + 92E46E91A7D47AEC5B2B2F55 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -21,10 +21,9 @@ 050E7C7319D22E19004363C2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 050E7C7519D22E19004363C2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; - 05DDD8DC19D2341D00013C30 /* AsyncDisplayKit-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Bridging-Header.h"; sourceTree = ""; }; - 2ACC614D420B4E90B7EE3BCE /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 6C5053D919EE266A00E385DE /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 841652076B3E9351337AA7C7 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; E3EE87D12CE3EF73FAE2EF02 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -34,7 +33,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4690009EF79C47BBA8FDBAD4 /* libPods.a in Frameworks */, + 92E46E91A7D47AEC5B2B2F55 /* Pods.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -75,7 +74,6 @@ 050E7C7119D22E19004363C2 /* Supporting Files */ = { isa = PBXGroup; children = ( - 05DDD8DC19D2341D00013C30 /* AsyncDisplayKit-Bridging-Header.h */, 050E7C7219D22E19004363C2 /* Info.plist */, 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */, 6C5053D919EE266A00E385DE /* Default-667h@2x.png */, @@ -87,7 +85,7 @@ 092C2001FE124604891D6E90 /* Frameworks */ = { isa = PBXGroup; children = ( - 2ACC614D420B4E90B7EE3BCE /* libPods.a */, + 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */, ); name = Frameworks; sourceTree = ""; @@ -113,6 +111,7 @@ 050E7C6B19D22E19004363C2 /* Frameworks */, 050E7C6C19D22E19004363C2 /* Resources */, 941C5E41C54B4613A2D3B760 /* Copy Pods Resources */, + 1F5A9F09F5875F61862D0783 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -171,6 +170,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1F5A9F09F5875F61862D0783 /* 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/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 941C5E41C54B4613A2D3B760 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -301,7 +315,6 @@ INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Sample/AsyncDisplayKit-Bridging-Header.h"; }; name = Debug; }; @@ -313,7 +326,6 @@ INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Sample/AsyncDisplayKit-Bridging-Header.h"; }; name = Release; }; diff --git a/examples/Swift/Sample/AsyncDisplayKit-Bridging-Header.h b/examples/Swift/Sample/AsyncDisplayKit-Bridging-Header.h deleted file mode 100644 index e5488e4ee6..0000000000 --- a/examples/Swift/Sample/AsyncDisplayKit-Bridging-Header.h +++ /dev/null @@ -1,12 +0,0 @@ -/* This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#import diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 037e0965e8..f907db6b93 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -10,6 +10,7 @@ */ import UIKit +import AsyncDisplayKit class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate { From 67212c69b76e7e3647ccccfc254c4aef26e350ba Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 11:04:39 -0800 Subject: [PATCH 010/224] Update Sample View Controller --- examples/Swift/Sample/AppDelegate.swift | 2 +- examples/Swift/Sample/ViewController.swift | 39 +++++++--------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/examples/Swift/Sample/AppDelegate.swift b/examples/Swift/Sample/AppDelegate.swift index a2b00b1727..3a1dac68c1 100644 --- a/examples/Swift/Sample/AppDelegate.swift +++ b/examples/Swift/Sample/AppDelegate.swift @@ -19,7 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { let window = UIWindow(frame: UIScreen.mainScreen().bounds) window.backgroundColor = UIColor.whiteColor() - window.rootViewController = ViewController(nibName: nil, bundle: nil) + window.rootViewController = ViewController() window.makeKeyAndVisible() self.window = window return true diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index f907db6b93..4158103571 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -12,55 +12,40 @@ import UIKit import AsyncDisplayKit -class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate { +final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate { - var tableView: ASTableView + var tableNode: ASTableNode { + return node as! ASTableNode + } - - // MARK: UIViewController. - - override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - self.tableView = ASTableView() - - super.init(nibName: nil, bundle: nil) - - self.tableView.asyncDataSource = self - self.tableView.asyncDelegate = self + init() { + super.init(node: ASTableNode()) + tableNode.delegate = self + tableNode.dataSource = self } required init?(coder aDecoder: NSCoder) { fatalError("storyboards are incompatible with truth and beauty") } - override func viewDidLoad() { - super.viewDidLoad() - self.view.addSubview(self.tableView) - } - - override func viewWillLayoutSubviews() { - self.tableView.frame = self.view.bounds - } - override func prefersStatusBarHidden() -> Bool { return true } - // MARK: ASTableView data source and delegate. - func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! { - let patter = NSString(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) + func tableView(tableView: ASTableView, nodeForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNode { let node = ASTextCellNode() - node.text = patter as String + node.text = String(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) return node } - func numberOfSectionsInTableView(tableView: UITableView!) -> Int { + func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } - func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } From 8f10d8ea998fc47dec9d7d05af5e37fde99f474b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 12:10:38 -0800 Subject: [PATCH 011/224] More progress with the Swift example --- examples/Kittens/Sample/ViewController.m | 34 ++++---- .../Swift/Sample.xcodeproj/project.pbxproj | 9 +- .../xcshareddata/xcschemes/Sample.xcscheme | 13 +-- .../contents.xcworkspacedata | 10 +++ examples/Swift/Sample/Info.plist | 2 +- .../Swift/Sample/TailLoadingCellNode.swift | 53 ++++++++++++ examples/Swift/Sample/ViewController.swift | 83 ++++++++++++++++++- 7 files changed, 177 insertions(+), 27 deletions(-) create mode 100644 examples/Swift/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/Swift/Sample/TailLoadingCellNode.swift diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 63df8deed9..2f2c7136ba 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -165,28 +165,24 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell - (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - sleep(1); - dispatch_async(dispatch_get_main_queue(), ^{ - - // populate a new array of random-sized kittens - NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // populate a new array of random-sized kittens + NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize]; - NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; - - // find number of kittens in the data source and create their indexPaths - NSInteger existingRows = _kittenDataSource.count + 1; - - for (NSInteger i = 0; i < moarKittens.count; i++) { - [indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]]; - } + NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + + // find number of kittens in the data source and create their indexPaths + NSInteger existingRows = _kittenDataSource.count + 1; + + for (NSInteger i = 0; i < moarKittens.count; i++) { + [indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]]; + } - // add new kittens to the data source & notify table of new indexpaths - [_kittenDataSource addObjectsFromArray:moarKittens]; - [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; + // add new kittens to the data source & notify table of new indexpaths + [_kittenDataSource addObjectsFromArray:moarKittens]; + [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching:YES]; - }); + [context completeBatchFetching:YES]; }); } diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index ac8d78cedf..18f8aba889 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053D919EE266A00E385DE /* Default-667h@2x.png */; }; 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */; }; 92E46E91A7D47AEC5B2B2F55 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */; }; + CCB01CAB1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -25,6 +26,7 @@ 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 841652076B3E9351337AA7C7 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailLoadingCellNode.swift; sourceTree = ""; }; E3EE87D12CE3EF73FAE2EF02 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -64,6 +66,7 @@ 050E7C7019D22E19004363C2 /* Sample */ = { isa = PBXGroup; children = ( + CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */, 050E7C7319D22E19004363C2 /* AppDelegate.swift */, 050E7C7519D22E19004363C2 /* ViewController.swift */, 050E7C7119D22E19004363C2 /* Supporting Files */, @@ -130,7 +133,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = Facebook; TargetAttributes = { 050E7C6D19D22E19004363C2 = { @@ -222,6 +225,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CCB01CAB1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift in Sources */, 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */, 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */, ); @@ -250,6 +254,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -314,6 +319,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -325,6 +331,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme index 8d7f73e325..f7f575e824 100644 --- a/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme +++ b/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,15 +38,18 @@ ReferencedContainer = "container:Sample.xcodeproj"> + + @@ -62,10 +65,10 @@ diff --git a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/Swift/Sample/Info.plist b/examples/Swift/Sample/Info.plist index 35d842827b..fb4115c84c 100644 --- a/examples/Swift/Sample/Info.plist +++ b/examples/Swift/Sample/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/examples/Swift/Sample/TailLoadingCellNode.swift b/examples/Swift/Sample/TailLoadingCellNode.swift new file mode 100644 index 0000000000..e6b4d333f0 --- /dev/null +++ b/examples/Swift/Sample/TailLoadingCellNode.swift @@ -0,0 +1,53 @@ +// +// TailLoadingCellNode.swift +// Sample +// +// Created by Adlai Holler on 2/1/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +import AsyncDisplayKit +import UIKit + +final class TailLoadingCellNode: ASCellNode { + let spinner = SpinnerNode() + let text = ASTextNode() + + override init() { + super.init() + addSubnode(text) + text.attributedString = NSAttributedString( + string: "Loading…", + attributes: [ + NSFontAttributeName: UIFont.systemFontOfSize(12), + NSForegroundColorAttributeName: UIColor.lightGrayColor(), + NSKernAttributeName: -0.3 + ]) + addSubnode(spinner) + } + + override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec { + return ASStackLayoutSpec( + direction: .Horizontal, + spacing: 16, + justifyContent: .Center, + alignItems: .Center, + children: [ text, spinner ]) + } +} + +final class SpinnerNode: ASDisplayNode { + var activityIndicatorView: UIActivityIndicatorView { + return view as! UIActivityIndicatorView + } + + override init() { + super.init(viewBlock: { UIActivityIndicatorView(activityIndicatorStyle: .Gray) }, didLoadBlock: nil) + preferredFrameSize.height = 32 + } + + override func didLoad() { + super.didLoad() + activityIndicatorView.startAnimating() + } +} \ No newline at end of file diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 4158103571..491478ccdb 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -14,10 +14,23 @@ import AsyncDisplayKit final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate { + struct State { + var rowCount: Int + var showingSpinner: Bool + static let empty = State(rowCount: 20, showingSpinner: false) + } + + enum Action { + case BeginBatchFetch + case EndBatchFetch(resultCount: Int) + } + var tableNode: ASTableNode { return node as! ASTableNode } + private(set) var state: State = .empty + init() { super.init(node: ASTableNode()) tableNode.delegate = self @@ -35,6 +48,11 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate // MARK: ASTableView data source and delegate. func tableView(tableView: ASTableView, nodeForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNode { + NSLog("Number of rows %d", tableView.numberOfRowsInSection(0)) + if state.showingSpinner && indexPath.row == tableView.numberOfRowsInSection(0) - 1 { + return TailLoadingCellNode() + } + let node = ASTextCellNode() node.text = String(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) @@ -46,7 +64,70 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 20 + var count = state.rowCount + if state.showingSpinner { + count += 1 + } + return count } + func tableView(tableView: ASTableView, willBeginBatchFetchWithContext context: ASBatchContext) { + context.cancelBatchFetching() + dispatch_async(dispatch_get_main_queue()) { + let oldState = self.state + self.state = ViewController.handleAction(.BeginBatchFetch, fromState: oldState) + self.render(oldState) + } + return; + + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(NSTimeInterval(NSEC_PER_SEC) * 3)) + dispatch_after(time, dispatch_get_main_queue()) { + let action = Action.EndBatchFetch(resultCount: 20) + let oldState = self.state + self.state = ViewController.handleAction(action, fromState: oldState) + self.render(oldState) + context.completeBatchFetching(true) + } + } + + func render(oldState: State) { + let tableView = tableNode.view + tableView.beginUpdates() + + // Add or remove items + let rowCountChange = state.rowCount - oldState.rowCount + if rowCountChange > 0 { + let indexPaths = (oldState.rowCount.. State { + switch action { + case .BeginBatchFetch: + state.showingSpinner = true + case let .EndBatchFetch(resultCount): + state.rowCount += resultCount + state.showingSpinner = false + } + return state + } } From 7cb430992e4f029086432f77fd8154614ab0b3ff Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 12:41:41 -0800 Subject: [PATCH 012/224] Finish up the new view controller --- examples/Swift/Sample/ViewController.swift | 62 +++++++++++++--------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 491478ccdb..8c1a0039db 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -15,9 +15,9 @@ import AsyncDisplayKit final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate { struct State { - var rowCount: Int - var showingSpinner: Bool - static let empty = State(rowCount: 20, showingSpinner: false) + var itemCount: Int + var fetchingMore: Bool + static let empty = State(itemCount: 20, fetchingMore: false) } enum Action { @@ -48,8 +48,11 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate // MARK: ASTableView data source and delegate. func tableView(tableView: ASTableView, nodeForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNode { - NSLog("Number of rows %d", tableView.numberOfRowsInSection(0)) - if state.showingSpinner && indexPath.row == tableView.numberOfRowsInSection(0) - 1 { + // Should read the row count directly from table view but + // https://github.com/facebook/AsyncDisplayKit/issues/1159 + let rowCount = self.tableView(tableView, numberOfRowsInSection: 0) + + if state.fetchingMore && indexPath.row == rowCount - 1 { return TailLoadingCellNode() } @@ -64,40 +67,39 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - var count = state.rowCount - if state.showingSpinner { + var count = state.itemCount + if state.fetchingMore { count += 1 } return count } func tableView(tableView: ASTableView, willBeginBatchFetchWithContext context: ASBatchContext) { - context.cancelBatchFetching() + /// This call will come in on a background thread. Switch to main + /// to add our spinner, then fire off our fetch. dispatch_async(dispatch_get_main_queue()) { let oldState = self.state self.state = ViewController.handleAction(.BeginBatchFetch, fromState: oldState) - self.render(oldState) + self.renderDiff(oldState) } - return; - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(NSTimeInterval(NSEC_PER_SEC) * 3)) - dispatch_after(time, dispatch_get_main_queue()) { + ViewController.fetchDataWithCompletion { resultCount in let action = Action.EndBatchFetch(resultCount: 20) let oldState = self.state self.state = ViewController.handleAction(action, fromState: oldState) - self.render(oldState) - context.completeBatchFetching(true) + self.renderDiff(oldState) + context.completeBatchFetching(true) } } - func render(oldState: State) { + private func renderDiff(oldState: State) { let tableView = tableNode.view tableView.beginUpdates() // Add or remove items - let rowCountChange = state.rowCount - oldState.rowCount + let rowCountChange = state.itemCount - oldState.itemCount if rowCountChange > 0 { - let indexPaths = (oldState.rowCount.. State { + /// (Pretend) fetches some new items and calls the + /// completion handler on the main thread. + private static func fetchDataWithCompletion(completion: (Int) -> Void) { + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(NSTimeInterval(NSEC_PER_SEC) * 0.5)) + dispatch_after(time, dispatch_get_main_queue()) { + let resultCount = Int(arc4random_uniform(20)) + completion(resultCount) + } + } + + private static func handleAction(action: Action, var fromState state: State) -> State { switch action { case .BeginBatchFetch: - state.showingSpinner = true + state.fetchingMore = true case let .EndBatchFetch(resultCount): - state.rowCount += resultCount - state.showingSpinner = false + state.itemCount += resultCount + state.fetchingMore = false } return state } From 819a09da703ce977a1cd706f301a8e8d2518e7d5 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 10:40:52 -0800 Subject: [PATCH 013/224] Make TextKit headers private in the podspec to fix use_frameworks! builds --- AsyncDisplayKit.podspec | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 974b390886..6a7aecf83c 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -13,13 +13,18 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/*.h', 'AsyncDisplayKit/Details/**/*.h', 'AsyncDisplayKit/Layout/*.h', - 'AsyncDisplayKit/TextKit/*.h', 'Base/*.h' ] spec.source_files = [ 'AsyncDisplayKit/**/*.{h,m,mm}', - 'Base/*.{h,m}' + 'Base/*.{h,m}', + + # TextKit components are not public because the C++ content + # in the headers will cause build errors when using + # `use_frameworks!` on 0.39.0 & Swift 2.1. + # See https://github.com/facebook/AsyncDisplayKit/issues/1153 + 'AsyncDisplayKit/TextKit/*.h', ] spec.frameworks = 'AssetsLibrary' From 3537476bd26d229d01bf88b50aca148040feb41f Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 13:11:59 -0800 Subject: [PATCH 014/224] Use the result count --- examples/Swift/Sample/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 8c1a0039db..dec8710f86 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -84,7 +84,7 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate } ViewController.fetchDataWithCompletion { resultCount in - let action = Action.EndBatchFetch(resultCount: 20) + let action = Action.EndBatchFetch(resultCount: resultCount) let oldState = self.state self.state = ViewController.handleAction(action, fromState: oldState) self.renderDiff(oldState) From 0f3f6f0dff5c432ca3128daf4cc3f35ec68fa063 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 1 Feb 2016 13:25:22 -0800 Subject: [PATCH 015/224] Fix ASTableView to have None direction --- AsyncDisplayKit/ASTableView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 31cbab7a77..3f2bfb1990 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -580,7 +580,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASScrollDirection direction = ASScrollDirectionNone; if (velocity.y > 0) { direction = ASScrollDirectionDown; - } else { + } else if (velocity.y < 0) { direction = ASScrollDirectionUp; } return direction; From 0b188c7a95c3b24b40bf04eb05172b5933d7ef10 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 1 Feb 2016 15:32:40 -0800 Subject: [PATCH 016/224] Add minimum tuning params --- AsyncDisplayKit/ASCollectionView.h | 5 --- AsyncDisplayKit/ASCollectionView.mm | 23 +++++------ .../Details/ASAbstractLayoutController.mm | 34 +++++++++++++++- .../ASCollectionViewLayoutController.mm | 23 ++--------- .../Details/ASFlowLayoutController.mm | 4 +- AsyncDisplayKit/Details/ASLayoutController.h | 11 +++-- AsyncDisplayKit/Details/ASRangeController.mm | 2 +- .../Details/ASRangeControllerBeta.mm | 40 ++++++++++--------- AsyncDisplayKit/Private/ASInternalHelpers.mm | 1 - 9 files changed, 79 insertions(+), 64 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index b1ff87f251..418d22c2d3 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -90,11 +90,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, weak) id layoutInspector; -/** - * The ASInterfaceState of this collection view. - */ -@property (nonatomic, readonly) ASInterfaceState interfaceState; - /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. * The asyncDataSource must be updated to reflect the changes before the update block completes. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index d177b49173..d8019a3e08 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -242,19 +242,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return _flowLayoutInspector; } -- (ASInterfaceState)interfaceState -{ - ASCollectionNode *collectionNode = self.collectionNode; - if (collectionNode) { - return collectionNode.interfaceState; - } else { - // Until we can always create an associated ASCollectionNode without a retain cycle, - // we might be on our own to try to guess if we're visible. The node normally - // handles this even if it is the root / directly added to the view hierarchy. - return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); - } -} - #pragma mark - #pragma mark Overrides. @@ -841,7 +828,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController { - return self.interfaceState; + ASCollectionNode *collectionNode = self.collectionNode; + if (collectionNode) { + return collectionNode.interfaceState; + } else { + // Until we can always create an associated ASCollectionNode without a retain cycle, + // we might be on our own to try to guess if we're visible. The node normally + // handles this even if it is the root / directly added to the view hierarchy. + return (self.window != nil ? ASInterfaceStateVisible : ASInterfaceStateNone); + } } - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 22c951278d..7a10ff1f38 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -12,6 +12,7 @@ @interface ASAbstractLayoutController () { std::vector _tuningParameters; + std::vector _minimumTuningParameters; CGSize _viewportSize; } @end @@ -38,6 +39,19 @@ .trailingBufferScreenfuls = 2 }; + _minimumTuningParameters = std::vector(ASLayoutRangeTypeCount); + _minimumTuningParameters[ASLayoutRangeTypeVisible] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 + }; + _minimumTuningParameters[ASLayoutRangeTypeDisplay] = { + .leadingBufferScreenfuls = 0.25, + .trailingBufferScreenfuls = 0.25 + }; + _minimumTuningParameters[ASLayoutRangeTypeFetchData] = { + .leadingBufferScreenfuls = 1, + .trailingBufferScreenfuls = 1 + }; return self; } @@ -57,6 +71,24 @@ _tuningParameters[rangeType] = tuningParameters; } +- (ASRangeTuningParameters)minimumTuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _minimumTuningParameters.size(), @"Requesting a range that is OOB for the configured minimum tuning parameters"); + return _minimumTuningParameters[rangeType]; +} + +- (void)setMinimumTuningParameters:(ASRangeTuningParameters)minimumTuningParameters forRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _minimumTuningParameters.size(), @"Requesting a range that is OOB for the configured minimum tuning parameters"); + ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range minimum tuning parameters (always 0, 0)"); + _minimumTuningParameters[rangeType] = minimumTuningParameters; +} + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType isFullRange:(BOOL)isFullRange +{ + return isFullRange ? [self tuningParametersForRangeType:rangeType] : [self minimumTuningParametersForRangeType:rangeType]; +} + #pragma mark - Abstract Index Path Range Support // FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. @@ -66,7 +98,7 @@ return NO; } -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange { ASDisplayNodeAssertNotSupported(); return nil; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index f62e54f1cc..6777c2d3de 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -66,9 +66,9 @@ typedef struct ASRangeGeometry ASRangeGeometry; return self; } -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange { - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection tuningParameters:tuningParameters]; _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds]; @@ -133,9 +133,9 @@ typedef struct ASRangeGeometry ASRangeGeometry; @implementation ASCollectionViewLayoutControllerBeta -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange { - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; return [self indexPathsForItemsWithinRangeBounds:rangeBounds]; } @@ -165,19 +165,4 @@ typedef struct ASRangeGeometry ASRangeGeometry; return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, _scrollableDirections, scrollDirection); } -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - BOOL isVisible = ASInterfaceStateIncludesVisible(_collectionView.interfaceState); - BOOL isScrolling = (_collectionView.scrollDirection != ASScrollDirectionNone); - // When the collecion view is not visible or not scrolling, make display range only as big as visible range. - // This reduce early creation of views and layers. - if (!isVisible || !isScrolling) { - if (rangeType == ASLayoutRangeTypeDisplay) { - return [super tuningParametersForRangeType:ASLayoutRangeTypeVisible]; - } - } - - return [super tuningParametersForRangeType:rangeType]; -} - @end diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index 0442fb68a5..d2b3a62bdc 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -74,7 +74,7 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; * IndexPath array for the element in the working range. */ -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange { CGFloat viewportScreenMetric; ASScrollDirection leadingDirection; @@ -92,7 +92,7 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; leadingDirection = ASScrollDirectionUp; } - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; CGFloat backScreens = scrollDirection == leadingDirection ? tuningParameters.leadingBufferScreenfuls : tuningParameters.trailingBufferScreenfuls; CGFloat frontScreens = scrollDirection == leadingDirection ? tuningParameters.trailingBufferScreenfuls : tuningParameters.leadingBufferScreenfuls; diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 674db701ee..1a3d89f263 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -25,17 +25,22 @@ typedef struct { /** * Tuning parameters for the range. - * - * Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls. */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; +- (void)setMinimumTuningParameters:(ASRangeTuningParameters)minimumTuningParameters forRangeType:(ASLayoutRangeType)rangeType; + +- (ASRangeTuningParameters)minimumTuningParametersForRangeType:(ASLayoutRangeType)rangeType; + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType isFullRange:(BOOL)isFullRange; + // FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. +// TODO: Now that it is the main version, can we remove this now? - (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType; -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType; +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange; @optional diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 0329ccfdf4..99a6492290 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -153,7 +153,7 @@ id rangeHandler = _rangeTypeHandlers[rangeKey]; if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths rangeType:rangeType]) { - NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:rangeType]; + NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:rangeType shouldUseFullRange:YES]; // Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths NSMutableSet *removedIndexPaths = _rangeIsValid ? [_rangeTypeIndexPaths[rangeKey] mutableCopy] : [NSMutableSet set]; diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 12627675a6..daf942c600 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -24,6 +24,7 @@ BOOL _layoutControllerImplementsSetVisibleIndexPaths; ASScrollDirection _scrollDirection; NSSet *_allPreviousIndexPaths; + BOOL _didUseFullRange; } @end @@ -105,27 +106,29 @@ NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - - if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { - // If we are already visible, get busy! Better get started on preloading before the user scrolls more... - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; + BOOL selfIsVisible = (ASInterfaceStateIncludesVisible(selfInterfaceState)); + BOOL selfIsScrolling = (_scrollDirection != ASScrollDirectionNone); + // If we are already visible and scrolling, get busy! Better get started on preloading before the user scrolls more... + // If we used full range, don't switch to minimum range now. That will destroy all the hard work done before. + BOOL shouldUseFullRange = ((selfIsVisible && selfIsScrolling) || _didUseFullRange); - ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData]; - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; - if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls && - parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) { - displayIndexPaths = fetchDataIndexPaths; - } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; - } - - // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. - // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. - // This means that during iteration, we will first visit visible, then display, then fetch data nodes. - [allIndexPaths unionSet:displayIndexPaths]; - [allIndexPaths unionSet:fetchDataIndexPaths]; + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData shouldUseFullRange:shouldUseFullRange]; + + ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay isFullRange:shouldUseFullRange]; + ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData isFullRange:shouldUseFullRange]; + if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls && + parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) { + displayIndexPaths = fetchDataIndexPaths; + } else { + displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay shouldUseFullRange:shouldUseFullRange]; } + // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. + // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. + // This means that during iteration, we will first visit visible, then display, then fetch data nodes. + [allIndexPaths unionSet:displayIndexPaths]; + [allIndexPaths unionSet:fetchDataIndexPaths]; + // Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic // scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all. @@ -133,6 +136,7 @@ NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; [allIndexPaths unionSet:_allPreviousIndexPaths]; _allPreviousIndexPaths = allCurrentIndexPaths; + _didUseFullRange = shouldUseFullRange; if (!_rangeIsValid) { [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 71ac09d21f..557d52119c 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -68,7 +68,6 @@ void ASPerformBlockOnBackgroundThread(void (^block)()) } } - CGFloat ASScreenScale() { static CGFloat _scale; From 184d1fc05997558e5acf28e665240dfc594ef521 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Thu, 28 Jan 2016 23:38:18 -0800 Subject: [PATCH 017/224] Switch layout flatten to BFS for node ordering --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- AsyncDisplayKit/Layout/ASLayout.mm | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index cd0cb2fb96..5caf4f49b9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -64,7 +64,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { - return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); + return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()) diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 745b325e52..4a48501a97 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -12,7 +12,7 @@ #import "ASAssert.h" #import "ASLayoutSpecUtilities.h" #import "ASInternalHelpers.h" -#import +#import CGPoint const CGPointNull = {NAN, NAN}; @@ -71,14 +71,14 @@ extern BOOL CGPointIsNull(CGPoint point) BOOL visited; }; - // Stack of Contexts, used to keep track of sublayouts while traversing this layout in a DFS fashion. - std::stack stack; - stack.push({self, CGPointMake(0, 0), NO}); + // Stack of Contexts, used to keep track of sublayouts while traversing this layout in a BFS fashion. + std::queue queue; + queue.push({self, CGPointMake(0, 0), NO}); - while (!stack.empty()) { - Context &context = stack.top(); + while (!queue.empty()) { + Context &context = queue.front(); if (context.visited) { - stack.pop(); + queue.pop(); } else { context.visited = YES; @@ -90,7 +90,7 @@ extern BOOL CGPointIsNull(CGPoint point) } for (ASLayout *sublayout in context.layout.sublayouts) { - stack.push({sublayout, context.absolutePosition + sublayout.position, NO}); + queue.push({sublayout, context.absolutePosition + sublayout.position, NO}); } } } From b2843d29c4859fea3ce3e0b807e2f01e23d51b6c Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Thu, 28 Jan 2016 23:54:05 -0800 Subject: [PATCH 018/224] Allow any node to be identified in the flattened predicate search --- AsyncDisplayKit/ASDisplayNode.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 5caf4f49b9..1983befffa 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1606,7 +1606,8 @@ static BOOL ShouldUseNewRenderingRange = YES; layout = [ASLayout layoutWithLayoutableObject:self size:layout.size sublayouts:@[layout]]; } return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { - return [_subnodes containsObject:evaluatedLayout.layoutableObject]; + return ASObjectIsEqual(layout, evaluatedLayout) == NO && + [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]]; }]; } else { // If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used, From 561ae212d9a14e5c56a6deb2cf8633627ab87b11 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 29 Jan 2016 10:50:49 -0800 Subject: [PATCH 019/224] Wrap implicit hierarchy management in a class enable bit --- AsyncDisplayKit/ASDisplayNode+Beta.h | 3 +++ AsyncDisplayKit/ASDisplayNode.mm | 21 +++++++++++++++++++-- AsyncDisplayKit/Layout/ASLayout.mm | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 378c060455..d31ee49504 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -11,6 +11,9 @@ + (BOOL)shouldUseNewRenderingRange; + (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange; ++ (BOOL)usesImplicitHierarchyManagement; ++ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled; + /** @name Layout */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 1983befffa..b554995181 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -52,6 +52,9 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; #endif @interface ASDisplayNode () <_ASDisplayLayerDelegate> + +@property (assign, nonatomic) BOOL implicitNodeHierarchyManagement; + @end @implementation ASDisplayNode @@ -62,6 +65,17 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; @synthesize preferredFrameSize = _preferredFrameSize; @synthesize isFinalLayoutable = _isFinalLayoutable; +static BOOL usesImplicitHierarchyManagement = FALSE; + ++ (BOOL)usesImplicitHierarchyManagement { + return usesImplicitHierarchyManagement; +} + ++ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled +{ + usesImplicitHierarchyManagement = enabled; +} + BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); @@ -1606,8 +1620,11 @@ static BOOL ShouldUseNewRenderingRange = YES; layout = [ASLayout layoutWithLayoutableObject:self size:layout.size sublayouts:@[layout]]; } return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { - return ASObjectIsEqual(layout, evaluatedLayout) == NO && - [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]]; + if ([[self class] usesImplicitHierarchyManagement]) { + return ASObjectIsEqual(layout, evaluatedLayout) == NO && [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]]; + } else { + return [_subnodes containsObject:evaluatedLayout.layoutableObject]; + } }]; } else { // If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used, diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 4a48501a97..e02e618c7a 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -94,7 +94,7 @@ extern BOOL CGPointIsNull(CGPoint point) } } } - + return [ASLayout layoutWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts]; } From 29609bfe87d0953c203b508bfe4ed945d0815356 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 29 Jan 2016 10:51:46 -0800 Subject: [PATCH 020/224] Clean up long lines --- AsyncDisplayKit/ASDisplayNode.mm | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b554995181..a3a572f570 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -622,16 +622,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssertTrue(_layout.size.width >= 0.0); ASDisplayNodeAssertTrue(_layout.size.height >= 0.0); - // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go - // also if a node has no size, it should not have a placeholder - if (self.placeholderEnabled && [self _displaysAsynchronously] && _layout.size.width > 0.0 && _layout.size.height > 0.0) { - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - - if (_placeholderLayer) { - [self setupPlaceholderLayerContents]; - } + // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed + // to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder + if (self.placeholderEnabled && [self _displaysAsynchronously] && + _layout.size.width > 0.0 && _layout.size.height > 0.0) { + [self __generatePlaceholder]; } return _layout; @@ -639,6 +634,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)calculatedLayoutDidChange { + // subclass override +} + +- (void)__generatePlaceholder +{ + if (!_placeholderImage) { + _placeholderImage = [self placeholderImage]; + } + + if (_placeholderLayer) { + [self setupPlaceholderLayerContents]; + } } - (BOOL)displaysAsynchronously @@ -819,7 +826,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); if (CGRectEqualToRect(self.bounds, CGRectZero)) { - return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measureWithSizeRange: on subnodes to assert. + // Performing layout on a zero-bounds view often results in frame calculations + // with negative sizes after applying margins, which will cause + // measureWithSizeRange: on subnodes to assert. + return; } _placeholderLayer.frame = self.bounds; [self layout]; From 822fc96f96cfd94f5bd227e48fbb8e41d969fc8a Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 29 Jan 2016 12:04:54 -0800 Subject: [PATCH 021/224] Add LCS diffing support to NSArray --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +++ AsyncDisplayKit/Private/NSArray+Diffing.h | 18 ++++++ AsyncDisplayKit/Private/NSArray+Diffing.m | 67 +++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 AsyncDisplayKit/Private/NSArray+Diffing.h create mode 100644 AsyncDisplayKit/Private/NSArray+Diffing.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 0091e38901..16393ae17f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -475,6 +475,8 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; + DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; @@ -802,6 +804,8 @@ D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; + DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; @@ -1164,6 +1168,8 @@ 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */, + DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, + DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, ); path = Private; sourceTree = ""; @@ -1354,6 +1360,7 @@ ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */, DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, + DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, @@ -1782,6 +1789,7 @@ ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, + DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */, diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.h b/AsyncDisplayKit/Private/NSArray+Diffing.h new file mode 100644 index 0000000000..618e1901ea --- /dev/null +++ b/AsyncDisplayKit/Private/NSArray+Diffing.h @@ -0,0 +1,18 @@ +// +// NSArray+Diffing.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 1/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface NSArray (Diffing) + +/** + * Uses a bottom-up memoized longest common subsequence solution to identify differences. Runs in O(mn) complexity. + */ +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSMutableIndexSet **)insertions deletions:(NSMutableIndexSet **)deletions; + +@end diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m new file mode 100644 index 0000000000..f320bcfb58 --- /dev/null +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -0,0 +1,67 @@ +// +// NSArray+Diffing.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 1/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "NSArray+Diffing.h" + +@implementation NSArray (Diffing) + +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSMutableIndexSet **)insertions deletions:(NSMutableIndexSet **)deletions +{ + NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array]; + + if (insertions) { + NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; + for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { + if (i < commonObjects.count && j < array.count && [commonObjects[i] isEqual:array[j]]) { + i++; j++; + } else { + [*insertions addIndex:j]; + j++; + } + } + } + + if (deletions) { + for (NSInteger i = 0; i < self.count; i++) { + if (![commonIndexes containsIndex:i]) { + [*deletions addIndex:i]; + } + } + } +} + +- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array +{ + NSInteger lengths[self.count+1][array.count+1]; + for (NSInteger i = self.count; i >= 0; i--) { + for (NSInteger j = array.count; j >= 0; j--) { + if (i == self.count || j == array.count) { + lengths[i][j] = 0; + } else if ([self[i] isEqual:array[j]]) { + lengths[i][j] = 1 + lengths[i+1][j+1]; + } else { + lengths[i][j] = MAX(lengths[i+1][j], lengths[i][j+1]); + } + } + } + + NSMutableIndexSet *common = [NSMutableIndexSet indexSet]; + for (NSInteger i = 0, j = 0; i < self.count && j < array.count;) { + if ([self[i] isEqual:array[j]]) { + [common addIndex:i]; + i++; j++; + } else if (lengths[i+1][j] >= lengths[i][j+1]) { + i++; + } else { + j++; + } + } + return common; +} + +@end From 7a3987a467a7052df1fc7e567f6591be81ad561c Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 29 Jan 2016 20:15:08 -0800 Subject: [PATCH 022/224] Add tests to LCS array category --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ AsyncDisplayKitTests/ArrayDiffingTests.m | 72 +++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 AsyncDisplayKitTests/ArrayDiffingTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 16393ae17f..fca5e26e5f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -477,6 +477,7 @@ DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; + DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; @@ -806,6 +807,7 @@ D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; + DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; @@ -1004,6 +1006,7 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, @@ -1899,6 +1902,7 @@ 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, + DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m new file mode 100644 index 0000000000..c83e841051 --- /dev/null +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -0,0 +1,72 @@ +// +// ArrayDiffingTests.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 1/29/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "NSArray+Diffing.h" + +@interface ArrayDiffingTests : XCTestCase + +@end + +@implementation ArrayDiffingTests + +- (void)testDiffing { + NSArray *tests = @[ + @[ + @[@"bob", @"alice", @"dave"], + @[@"bob", @"alice", @"dave", @"gary"], + @[@3], + @[], + ], + @[ + @[@"bob", @"alice", @"dave"], + @[@"bob", @"gary", @"alice", @"dave"], + @[@1], + @[], + ], + @[ + @[@"bob", @"alice", @"dave"], + @[@"bob", @"alice"], + @[], + @[@2], + ], + @[ + @[@"bob", @"alice", @"dave"], + @[], + @[], + @[@0, @1, @2], + ], + @[ + @[@"bob", @"alice", @"dave"], + @[@"gary", @"alice", @"dave", @"jack"], + @[@0, @3], + @[@0], + ], + @[ + @[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"], + @[@"gary", @"bob", @"suzy", @"tony"], + @[@0, @2], + @[@1, @2, @3, @4], + ], + ]; + + for (NSArray *test in tests) { + NSMutableIndexSet *insertions = [NSMutableIndexSet indexSet]; + NSMutableIndexSet *deletions = [NSMutableIndexSet indexSet]; + [test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions]; + for (NSNumber *index in (NSArray *)test[2]) { + XCTAssert([insertions containsIndex:[index integerValue]]); + } + for (NSNumber *index in (NSArray *)test[3]) { + XCTAssert([deletions containsIndex:[index integerValue]]); + } + } +} + +@end From 3abe6d9181c2d3ea8ed4c7da3d4fbdb05a1be6fc Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Sun, 31 Jan 2016 20:59:59 -0800 Subject: [PATCH 023/224] Simplify measure call structure --- AsyncDisplayKit/ASDisplayNode.mm | 7 +------ AsyncDisplayKit/Private/ASDisplayNodeInternal.h | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a3a572f570..4d57bf1a48 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -596,14 +596,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize -{ - ASDN::MutexLocker l(_propertyLock); - return [self __measureWithSizeRange:constrainedSize]; -} - -- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize { ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); if (![self __shouldSize]) return nil; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index d688289e15..4b72f2c097 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -131,9 +131,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) - (BOOL)__shouldLoadViewOrLayer; - (BOOL)__shouldSize; -// Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held. -- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize; - +// Invoked by a call to setNeedsLayout to the underlying view - (void)__setNeedsLayout; - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; From 924e72f7740099493f5c8a88f95713fc23f3e4cb Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Sun, 31 Jan 2016 21:01:41 -0800 Subject: [PATCH 024/224] Mark setup placeholder method as private --- AsyncDisplayKit/ASDisplayNode.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 4d57bf1a48..032e2fecd4 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -639,7 +639,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } if (_placeholderLayer) { - [self setupPlaceholderLayerContents]; + [self _setupPlaceholderLayerContents]; } } @@ -2035,14 +2035,14 @@ static BOOL ShouldUseNewRenderingRange = YES; if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) { [CATransaction begin]; [CATransaction setDisableActions:YES]; - [self setupPlaceholderLayerContents]; + [self _setupPlaceholderLayerContents]; _placeholderLayer.opacity = 1.0; [CATransaction commit]; [self.layer addSublayer:_placeholderLayer]; } } -- (void)setupPlaceholderLayerContents +- (void)_setupPlaceholderLayerContents { BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); if (stretchable) { From e852cb612c0b1d5b26818fae0b3f2e7f83b7ff85 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Sun, 31 Jan 2016 21:17:24 -0800 Subject: [PATCH 025/224] Simplify usage of diffing API --- AsyncDisplayKit/ASDisplayNode.mm | 1 + AsyncDisplayKit/Private/NSArray+Diffing.h | 2 +- AsyncDisplayKit/Private/NSArray+Diffing.m | 10 +++++++--- AsyncDisplayKitTests/ArrayDiffingTests.m | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 032e2fecd4..bfd4cd3584 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -21,6 +21,7 @@ #import "_ASCoreAnimationExtras.h" #import "ASDisplayNodeExtras.h" #import "ASEqualityHelpers.h" +#import "NSArray+Diffing.h" #import "ASInternalHelpers.h" #import "ASLayout.h" diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.h b/AsyncDisplayKit/Private/NSArray+Diffing.h index 618e1901ea..374096f92f 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.h +++ b/AsyncDisplayKit/Private/NSArray+Diffing.h @@ -13,6 +13,6 @@ /** * Uses a bottom-up memoized longest common subsequence solution to identify differences. Runs in O(mn) complexity. */ -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSMutableIndexSet **)insertions deletions:(NSMutableIndexSet **)deletions; +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions; @end diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m index f320bcfb58..0cd9ad040b 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -10,28 +10,32 @@ @implementation NSArray (Diffing) -- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSMutableIndexSet **)insertions deletions:(NSMutableIndexSet **)deletions +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions { NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array]; if (insertions) { NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; + NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { if (i < commonObjects.count && j < array.count && [commonObjects[i] isEqual:array[j]]) { i++; j++; } else { - [*insertions addIndex:j]; + [insertionIndexes addIndex:j]; j++; } } + *insertions = insertionIndexes; } if (deletions) { + NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet]; for (NSInteger i = 0; i < self.count; i++) { if (![commonIndexes containsIndex:i]) { - [*deletions addIndex:i]; + [deletionIndexes addIndex:i]; } } + *deletions = deletionIndexes; } } diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m index c83e841051..d2224e0baa 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -57,8 +57,7 @@ ]; for (NSArray *test in tests) { - NSMutableIndexSet *insertions = [NSMutableIndexSet indexSet]; - NSMutableIndexSet *deletions = [NSMutableIndexSet indexSet]; + NSIndexSet *insertions, *deletions; [test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions]; for (NSNumber *index in (NSArray *)test[2]) { XCTAssert([insertions containsIndex:[index integerValue]]); From 9f25b54f9eb6d6e7b36da987706304526aa577e3 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 1 Feb 2016 10:51:01 -0800 Subject: [PATCH 026/224] Support insertion on first layout of display node --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ AsyncDisplayKit/ASDisplayNode.mm | 34 +++++++++- .../Private/ASDisplayNodeInternal.h | 7 +- .../ASDisplayNodeImplicitHierarchyTests.m | 66 +++++++++++++++++++ 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index fca5e26e5f..5f44fe5f5e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -478,6 +478,7 @@ DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; + DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; @@ -808,6 +809,7 @@ DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; + DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeImplicitHierarchyTests.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; @@ -1006,6 +1008,7 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, @@ -1901,6 +1904,7 @@ 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, + DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */, ); diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index bfd4cd3584..aaf6fcb61a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -608,7 +608,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // - we haven't already // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - _layout = [self calculateLayoutThatFits:constrainedSize]; + ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; + + if (_layout) { + NSIndexSet *insertions, *deletions; + [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions]; + _insertedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:insertions]; + _deletedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:deletions]; + } else { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; + _insertedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:indexes]; + _deletedSubnodes = @[]; + } + + _layout = newLayout; _constrainedSize = constrainedSize; _flags.isMeasured = YES; [self calculatedLayoutDidChange]; @@ -628,6 +641,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _layout; } +- (NSArray *)_filterLayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes +{ + NSMutableArray *result = [NSMutableArray array]; + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject; + ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); + [result addObject:node]; + }]; + return result; +} + - (void)calculatedLayoutDidChange { // subclass override @@ -1998,7 +2022,9 @@ static BOOL ShouldUseNewRenderingRange = YES; ASDisplayNode *subnode = nil; CGRect subnodeFrame = CGRectZero; for (ASLayout *subnodeLayout in _layout.sublayouts) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); + if (![[self class] usesImplicitHierarchyManagement]) { + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); + } CGPoint adjustedOrigin = subnodeLayout.position; if (isfinite(adjustedOrigin.x) == NO) { ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); @@ -2024,6 +2050,10 @@ static BOOL ShouldUseNewRenderingRange = YES; subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject); [subnode setFrame:subnodeFrame]; } + + for (ASDisplayNode *node in _insertedSubnodes) { + [self addSubnode:node]; + } } - (void)displayWillStart diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 4b72f2c097..6d3704f929 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -59,6 +59,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; + NSArray *_insertedSubnodes; + NSArray *_deletedSubnodes; ASDisplayNodeViewBlock _viewBlock; ASDisplayNodeLayerBlock _layerBlock; @@ -131,8 +133,11 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) - (BOOL)__shouldLoadViewOrLayer; - (BOOL)__shouldSize; -// Invoked by a call to setNeedsLayout to the underlying view +/** + Invoked by a call to setNeedsLayout to the underlying view + */ - (void)__setNeedsLayout; + - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m new file mode 100644 index 0000000000..71f318e771 --- /dev/null +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -0,0 +1,66 @@ +// +// ASDisplayNodeImplicitHierarchyTests.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/1/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "ASDisplayNode.h" +#import "ASDisplayNode+Beta.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASStaticLayoutSpec.h" + +@interface ASSpecTestDisplayNode : ASDisplayNode + +@property (copy, nonatomic) ASLayoutSpec * (^layoutSpecBlock)(ASSizeRange constrainedSize); + +@end + +@implementation ASSpecTestDisplayNode + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + return self.layoutSpecBlock(constrainedSize); +} + +@end + +@interface ASDisplayNodeImplicitHierarchyTests : XCTestCase + +@end + +@implementation ASDisplayNodeImplicitHierarchyTests + +- (void)setUp { + [super setUp]; + [ASDisplayNode setUsesImplicitHierarchyManagement:YES]; +} + +- (void)tearDown { + [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; + [super tearDown]; +} + +- (void)testFeatureFlag +{ + XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]); +} + +- (void)testInitialNodeInsertion +{ + ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.layoutSpecBlock = ^(ASSizeRange constrainedSize){ + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, node2]]; + }; + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + [node layout]; // Layout immediately + XCTAssertEqual(node.subnodes[0], node1); + XCTAssertEqual(node.subnodes[1], node2); +} + +@end From bd1de07c77e22531eef19ef4e6cd7470dbc581f3 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 1 Feb 2016 15:31:46 -0800 Subject: [PATCH 027/224] Add custom comparision block to array diffing category --- AsyncDisplayKit/Private/NSArray+Diffing.h | 13 ++++++++++++- AsyncDisplayKit/Private/NSArray+Diffing.m | 15 +++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.h b/AsyncDisplayKit/Private/NSArray+Diffing.h index 374096f92f..a549d45f49 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.h +++ b/AsyncDisplayKit/Private/NSArray+Diffing.h @@ -11,8 +11,19 @@ @interface NSArray (Diffing) /** - * Uses a bottom-up memoized longest common subsequence solution to identify differences. Runs in O(mn) complexity. + * @abstract Compares two arrays, providing the insertion and deletion indexes needed to transform into the target array. + * @discussion This compares the equality of each object with `isEqual:`. + * This diffing algorithm uses a bottom-up memoized longest common subsequence solution to identify differences. + * It runs in O(mn) complexity. */ - (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions; +/** + * @abstract Compares two arrays, providing the insertion and deletion indexes needed to transform into the target array. + * @discussion The `compareBlock` is used to identify the equality of the objects within the arrays. + * This diffing algorithm uses a bottom-up memoized longest common subsequence solution to identify differences. + * It runs in O(mn) complexity. + */ +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions compareBlock:(BOOL (^)(id lhs, id rhs))comparison; + @end diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m index 0cd9ad040b..00893d1416 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -12,13 +12,20 @@ - (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions { - NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array]; + [self asdk_diffWithArray:array insertions:insertions deletions:deletions compareBlock:^BOOL(id lhs, id rhs) { + return [lhs isEqual:rhs]; + }]; +} + +- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions compareBlock:(BOOL (^)(id lhs, id rhs))comparison +{ + NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison]; if (insertions) { NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { - if (i < commonObjects.count && j < array.count && [commonObjects[i] isEqual:array[j]]) { + if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { i++; j++; } else { [insertionIndexes addIndex:j]; @@ -39,7 +46,7 @@ } } -- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array +- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison { NSInteger lengths[self.count+1][array.count+1]; for (NSInteger i = self.count; i >= 0; i--) { @@ -56,7 +63,7 @@ NSMutableIndexSet *common = [NSMutableIndexSet indexSet]; for (NSInteger i = 0, j = 0; i < self.count && j < array.count;) { - if ([self[i] isEqual:array[j]]) { + if (comparison(self[i], array[j])) { [common addIndex:i]; i++; j++; } else if (lengths[i+1][j] >= lengths[i][j+1]) { From d168ec78ceddc4e72ac2b5c4fe2c7fcb79fc023e Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 1 Feb 2016 17:49:03 -0800 Subject: [PATCH 028/224] Implement simple, in-order add/remove subnode support when changing layout specs --- AsyncDisplayKit/ASDisplayNode.mm | 81 ++++++++++++++++--- .../Private/ASDisplayNodeInternal.h | 9 ++- .../ASDisplayNodeImplicitHierarchyTests.m | 73 +++++++++++++++-- 3 files changed, 145 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index aaf6fcb61a..8fb345bb5f 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -30,6 +30,36 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; +@interface _ASDisplayNodePosition : NSObject + +@property (nonatomic, assign) NSUInteger index; +@property (nonatomic, strong) ASDisplayNode *node; + ++ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index; + +- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index; + +@end + +@implementation _ASDisplayNodePosition + ++ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index +{ + return [[self alloc] initWithNode:node atIndex:index]; +} + +- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index +{ + self = [super init]; + if (self) { + _node = node; + _index = index; + } + return self; +} + +@end + @interface ASDisplayNode () /** @@ -609,18 +639,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; - + if (_layout) { NSIndexSet *insertions, *deletions; - [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions]; - _insertedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:insertions]; - _deletedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:deletions]; + [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); + }]; + _insertedSubnodes = [self _filterSublayouts:newLayout.sublayouts withIndexes:insertions]; + _deletedSubnodes = [self _filterSublayouts:_layout.sublayouts withIndexes:deletions]; } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; - _insertedSubnodes = [self _filterLayouts:newLayout.sublayouts withIndexes:indexes]; + _insertedSubnodes = [self _filterSublayouts:newLayout.sublayouts withIndexes:indexes]; _deletedSubnodes = @[]; } - + _layout = newLayout; _constrainedSize = constrainedSize; _flags.isMeasured = YES; @@ -641,13 +673,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _layout; } -- (NSArray *)_filterLayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes +- (NSArray<_ASDisplayNodePosition *> *)_filterSublayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes { - NSMutableArray *result = [NSMutableArray array]; + NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject; ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); - [result addObject:node]; + [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; }]; return result; } @@ -2051,11 +2083,38 @@ static BOOL ShouldUseNewRenderingRange = YES; [subnode setFrame:subnodeFrame]; } - for (ASDisplayNode *node in _insertedSubnodes) { - [self addSubnode:node]; + if ([[self class] usesImplicitHierarchyManagement]) { + if (!_managedSubnodes) { + _managedSubnodes = [NSMutableArray array]; + } + + for (_ASDisplayNodePosition *position in _deletedSubnodes) { + [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; + } + + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + } } } +- (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx +{ + ASDisplayNodeAssert(idx <= [_managedSubnodes count], @"index needs to be in range of the current managed subnodes"); + if (idx == [_managedSubnodes count]) { + [_managedSubnodes addObject:node]; + } else { + [_managedSubnodes insertObject:node atIndex:idx]; + } + [self addSubnode:node]; +} + +- (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx +{ + [_managedSubnodes removeObjectAtIndex:idx]; + [node removeFromSupernode]; +} + - (void)displayWillStart { // in case current node takes longer to display than it's subnodes, treat it as a dependent node diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 6d3704f929..e0740fd3fc 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -35,6 +35,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) }; @class _ASPendingState; +@class _ASDisplayNodePosition; // Allow 2^n increments of begin disabling hierarchy notifications #define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4 @@ -59,8 +60,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; - NSArray *_insertedSubnodes; - NSArray *_deletedSubnodes; + + // Subnodes implicitly managed by layout changes + NSMutableArray *_managedSubnodes; + + NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; + NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; ASDisplayNodeViewBlock _viewBlock; ASDisplayNodeLayerBlock _layerBlock; diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 71f318e771..c565297d7c 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -11,19 +11,35 @@ #import "ASDisplayNode.h" #import "ASDisplayNode+Beta.h" #import "ASDisplayNode+Subclasses.h" + #import "ASStaticLayoutSpec.h" +#import "ASStackLayoutSpec.h" @interface ASSpecTestDisplayNode : ASDisplayNode -@property (copy, nonatomic) ASLayoutSpec * (^layoutSpecBlock)(ASSizeRange constrainedSize); +@property (copy, nonatomic) ASLayoutSpec * (^layoutSpecBlock)(ASSizeRange constrainedSize, NSNumber *layoutState); + +/** + Simple state identifier to allow control of current spec inside of the layoutSpecBlock + */ +@property (strong, nonatomic) NSNumber *layoutState; @end @implementation ASSpecTestDisplayNode +- (instancetype)init +{ + self = [super init]; + if (self) { + _layoutState = @1; + } + return self; +} + - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - return self.layoutSpecBlock(constrainedSize); + return self.layoutSpecBlock(constrainedSize, _layoutState); } @end @@ -49,18 +65,65 @@ XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]); } -- (void)testInitialNodeInsertion +- (void)testInitialNodeInsertionWithOrdering { ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node4 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node5 = [[ASDisplayNode alloc] init]; + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; - node.layoutSpecBlock = ^(ASSizeRange constrainedSize){ - return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, node2]]; + node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState) { + ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]]; + + ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init]; + [stack1 setChildren:@[node1, node2]]; + + ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init]; + [stack2 setChildren:@[node3, staticLayout]]; + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[stack1, stack2, node5]]; }; + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + [node layout]; // Layout immediately + XCTAssertEqual(node.subnodes[0], node5); + XCTAssertEqual(node.subnodes[1], node1); + XCTAssertEqual(node.subnodes[2], node2); + XCTAssertEqual(node.subnodes[3], node3); + XCTAssertEqual(node.subnodes[4], node4); +} + +- (void)testCalculatedLayoutHierarchyTransitions +{ + ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; + + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState){ + if ([layoutState isEqualToNumber:@1]) { + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, node2]]; + } else { + ASStackLayoutSpec *stackLayout = [[ASStackLayoutSpec alloc] init]; + [stackLayout setChildren:@[node3, node2]]; + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, stackLayout]]; + } + }; + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; [node layout]; // Layout immediately XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); + + node.layoutState = @2; + [node invalidateCalculatedLayout]; // TODO(levi): Look into a way where measureWithSizeRange resizes when a new hierarchy is introduced but the size has not changed + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + [node layout]; // Layout immediately + + XCTAssertEqual(node.subnodes[0], node1); + XCTAssertEqual(node.subnodes[1], node3); + XCTAssertEqual(node.subnodes[2], node2); } @end From ac3c9d220beb89866fe91dd15d69ffb7be65a7b5 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 1 Feb 2016 18:29:50 -0800 Subject: [PATCH 029/224] Respond to review comments --- AsyncDisplayKit/ASDisplayNode.mm | 48 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 8fb345bb5f..18753cf85c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -98,7 +98,8 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; static BOOL usesImplicitHierarchyManagement = FALSE; -+ (BOOL)usesImplicitHierarchyManagement { ++ (BOOL)usesImplicitHierarchyManagement +{ return usesImplicitHierarchyManagement; } @@ -645,11 +646,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - _insertedSubnodes = [self _filterSublayouts:newLayout.sublayouts withIndexes:insertions]; - _deletedSubnodes = [self _filterSublayouts:_layout.sublayouts withIndexes:deletions]; + _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; + _deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; - _insertedSubnodes = [self _filterSublayouts:newLayout.sublayouts withIndexes:indexes]; + _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; _deletedSubnodes = @[]; } @@ -667,13 +668,19 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder if (self.placeholderEnabled && [self _displaysAsynchronously] && _layout.size.width > 0.0 && _layout.size.height > 0.0) { - [self __generatePlaceholder]; + if (!_placeholderImage) { + _placeholderImage = [self placeholderImage]; + } + + if (_placeholderLayer) { + [self _setupPlaceholderLayerContents]; + } } return _layout; } -- (NSArray<_ASDisplayNodePosition *> *)_filterSublayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes +- (NSArray<_ASDisplayNodePosition *> *)_filterNodesInLayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes { NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { @@ -689,17 +696,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // subclass override } -- (void)__generatePlaceholder -{ - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - - if (_placeholderLayer) { - [self _setupPlaceholderLayerContents]; - } -} - - (BOOL)displaysAsynchronously { ASDN::MutexLocker l(_propertyLock); @@ -2055,7 +2051,7 @@ static BOOL ShouldUseNewRenderingRange = YES; CGRect subnodeFrame = CGRectZero; for (ASLayout *subnodeLayout in _layout.sublayouts) { if (![[self class] usesImplicitHierarchyManagement]) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); } CGPoint adjustedOrigin = subnodeLayout.position; if (isfinite(adjustedOrigin.x) == NO) { @@ -2084,10 +2080,6 @@ static BOOL ShouldUseNewRenderingRange = YES; } if ([[self class] usesImplicitHierarchyManagement]) { - if (!_managedSubnodes) { - _managedSubnodes = [NSMutableArray array]; - } - for (_ASDisplayNodePosition *position in _deletedSubnodes) { [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; } @@ -2100,6 +2092,12 @@ static BOOL ShouldUseNewRenderingRange = YES; - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { + ASDisplayNodeAssertThreadAffinity(self); + + if (!_managedSubnodes) { + _managedSubnodes = [NSMutableArray array]; + } + ASDisplayNodeAssert(idx <= [_managedSubnodes count], @"index needs to be in range of the current managed subnodes"); if (idx == [_managedSubnodes count]) { [_managedSubnodes addObject:node]; @@ -2111,6 +2109,12 @@ static BOOL ShouldUseNewRenderingRange = YES; - (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { + ASDisplayNodeAssertThreadAffinity(self); + + if (!_managedSubnodes) { + _managedSubnodes = [NSMutableArray array]; + } + [_managedSubnodes removeObjectAtIndex:idx]; [node removeFromSupernode]; } From 89eae1213dace4bd9b2b28659b4eeb8c87b8fb72 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 1 Feb 2016 20:09:26 -0800 Subject: [PATCH 030/224] Delay set userInteractionEnabled to NO until didLoad --- AsyncDisplayKit/ASControlNode+Subclasses.h | 6 ++++++ AsyncDisplayKit/ASControlNode.m | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode+Subclasses.h b/AsyncDisplayKit/ASControlNode+Subclasses.h index 107de031e0..9257911150 100644 --- a/AsyncDisplayKit/ASControlNode+Subclasses.h +++ b/AsyncDisplayKit/ASControlNode+Subclasses.h @@ -19,6 +19,12 @@ NS_ASSUME_NONNULL_BEGIN @interface ASControlNode (Subclassing) +/** + @abstract Indicates whether or not at least one target was added to the receiver + @discussion YES if the receiver has at least one target; NO otherwise. + */ +@property (nonatomic, readonly, assign, getter=isTargetAdded) BOOL targetAdded; + /** @abstract Sends action messages for the given control events. @param controlEvents A bitmask whose set flags specify the control events for which action messages are sent. See "Control Events" in ASControlNode.h for bitmask constants. diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index f68fd3a02e..cf1c98ab47 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -7,6 +7,7 @@ */ #import "ASControlNode.h" +#import "ASDisplayNode+Subclasses.h" #import "ASControlNode+Subclasses.h" // UIControl allows dragging some distance outside of the control itself during @@ -22,6 +23,7 @@ { @private // Control Attributes + BOOL _targetAdded; BOOL _enabled; BOOL _highlighted; @@ -46,6 +48,7 @@ } // Read-write overrides. +@property (nonatomic, readwrite, assign, getter=isTargetAdded) BOOL targetAdded; @property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; @@ -77,12 +80,19 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v _enabled = YES; - // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - self.userInteractionEnabled = NO; return self; } #pragma mark - ASDisplayNode Overrides +- (void)didLoad +{ + [super didLoad]; + + // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. + if (!self.targetAdded) { + self.userInteractionEnabled = NO; + } +} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. From d1c2da07e77af2cda846cfead1ceccdd6632d666 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 1 Feb 2016 20:11:37 -0800 Subject: [PATCH 031/224] No need to set userInteractionEnabled in the ASTextNode init method The userInteractionEnabled state will be set by ASControlNode in viewDidLoad to the fitting value. --- AsyncDisplayKit/ASTextNode.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 99f8f716d7..be732e5508 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -104,8 +104,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _shadowOpacity = [super shadowOpacity]; _shadowRadius = [super shadowRadius]; - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; self.needsDisplayOnBoundsChange = YES; _truncationMode = NSLineBreakByWordWrapping; From ddf50b20bd216a1762e4543561c783bf18e776b6 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 1 Feb 2016 20:13:23 -0800 Subject: [PATCH 032/224] Use is target added state additionally to the enabled state to check if touches should be tracked --- AsyncDisplayKit/ASControlNode.m | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index cf1c98ab47..fa6c6f3914 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -93,10 +93,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v self.userInteractionEnabled = NO; } } + +- (BOOL)shouldTrackTouches +{ + return self.isTargetAdded && self.enabled; +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + if (!self.shouldTrackTouches) return; ASControlNodeEvent controlEventMask = 0; @@ -132,7 +138,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + if (!self.shouldTrackTouches) return; NSParameterAssert([touches count] == 1); @@ -158,7 +164,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + if (!self.shouldTrackTouches) return; // We're no longer tracking and there is no touch to be inside. @@ -177,7 +183,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.enabled) + if (!self.shouldTrackTouches) return; // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: @@ -264,6 +270,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [targetActions addObject:NSStringFromSelector(action)]; }); + self.targetAdded = YES; self.userInteractionEnabled = YES; } From 46b1f9fa8c95215b3199db55844fbb389f022826 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 23:00:51 -0800 Subject: [PATCH 033/224] Make ASTextNodeTypes.h public --- AsyncDisplayKit.podspec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 6a7aecf83c..f93552b3c0 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -13,14 +13,15 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/*.h', 'AsyncDisplayKit/Details/**/*.h', 'AsyncDisplayKit/Layout/*.h', - 'Base/*.h' + 'Base/*.h', + 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h' ] spec.source_files = [ 'AsyncDisplayKit/**/*.{h,m,mm}', 'Base/*.{h,m}', - # TextKit components are not public because the C++ content + # Most TextKit components are not public because the C++ content # in the headers will cause build errors when using # `use_frameworks!` on 0.39.0 & Swift 2.1. # See https://github.com/facebook/AsyncDisplayKit/issues/1153 From 1145b6e40ea6bcc8571a14840be7e493c33f802b Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 1 Feb 2016 22:14:03 -0800 Subject: [PATCH 034/224] Better decision on when to use full range --- .../Details/ASRangeControllerBeta.mm | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index daf942c600..a8d77c2301 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -17,6 +17,12 @@ #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" +typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { + ASRangeTypeUsedNone, + ASRangeTypeUsedMinimum, + ASRangeTypeUsedFull, +}; + @interface ASRangeControllerBeta () { BOOL _rangeIsValid; @@ -24,7 +30,7 @@ BOOL _layoutControllerImplementsSetVisibleIndexPaths; ASScrollDirection _scrollDirection; NSSet *_allPreviousIndexPaths; - BOOL _didUseFullRange; + ASRangeTypeUsed _rangeTypeUsed; } @end @@ -38,6 +44,7 @@ } _rangeIsValid = YES; + _rangeTypeUsed = ASRangeTypeUsedNone; return self; } @@ -47,11 +54,15 @@ - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection { _scrollDirection = scrollDirection; + [self scheduleRangeUpdate]; +} +- (void)scheduleRangeUpdate +{ if (_queuedRangeUpdate) { return; } - + // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; @@ -108,19 +119,23 @@ ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; BOOL selfIsVisible = (ASInterfaceStateIncludesVisible(selfInterfaceState)); BOOL selfIsScrolling = (_scrollDirection != ASScrollDirectionNone); + BOOL didUseMinimumRange = (_rangeTypeUsed == ASRangeTypeUsedMinimum); + BOOL didUseFullRange = (_rangeTypeUsed == ASRangeTypeUsedFull); // If we are already visible and scrolling, get busy! Better get started on preloading before the user scrolls more... + // If we are already visible and did finish displaying minimum range, extend to full range // If we used full range, don't switch to minimum range now. That will destroy all the hard work done before. - BOOL shouldUseFullRange = ((selfIsVisible && selfIsScrolling) || _didUseFullRange); + BOOL useFullRange = ((selfIsVisible && (selfIsScrolling || didUseMinimumRange)) || didUseFullRange); + NSLog(@"%@ range: %@", useFullRange ? @"Full" : @"Minimum", [((ASCollectionView *)_delegate).asyncDelegate description]); - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData shouldUseFullRange:shouldUseFullRange]; + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData shouldUseFullRange:useFullRange]; - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay isFullRange:shouldUseFullRange]; - ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData isFullRange:shouldUseFullRange]; + ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay isFullRange:useFullRange]; + ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData isFullRange:useFullRange]; if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls && parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) { displayIndexPaths = fetchDataIndexPaths; } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay shouldUseFullRange:shouldUseFullRange]; + displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay shouldUseFullRange:useFullRange]; } // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. @@ -136,7 +151,7 @@ NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; [allIndexPaths unionSet:_allPreviousIndexPaths]; _allPreviousIndexPaths = allCurrentIndexPaths; - _didUseFullRange = shouldUseFullRange; + _rangeTypeUsed = useFullRange ? ASRangeTypeUsedFull : ASRangeTypeUsedMinimum; if (!_rangeIsValid) { [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; From 69e674c1c85c64d0186fff2f6fba1010cea5aa76 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 1 Feb 2016 23:42:13 -0800 Subject: [PATCH 035/224] Range controller registers to rendering engine and extern to full range if needed --- AsyncDisplayKit/ASDisplayNode.mm | 3 + .../Details/ASAbstractLayoutController.mm | 70 ++++++------- .../ASCollectionViewLayoutController.mm | 8 +- .../Details/ASFlowLayoutController.mm | 4 +- AsyncDisplayKit/Details/ASLayoutController.h | 29 ++++-- AsyncDisplayKit/Details/ASRangeController.mm | 4 +- .../Details/ASRangeControllerBeta.mm | 99 ++++++++++++++----- .../Private/ASDisplayNodeInternal.h | 2 + 8 files changed, 143 insertions(+), 76 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 5f0cdfd548..7d8e40c217 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -29,6 +29,7 @@ #import "ASCellNode.h" NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; +NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; @interface _ASDisplayNodePosition : NSObject @@ -279,6 +280,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) for (ASDisplayNode *node in displayingNodes) { [node __recursivelyTriggerDisplayAndBlock:NO]; } + [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil]; }); } } diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 7a10ff1f38..7b1f107413 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -11,8 +11,7 @@ #include @interface ASAbstractLayoutController () { - std::vector _tuningParameters; - std::vector _minimumTuningParameters; + std::vector> _tuningParameters; CGSize _viewportSize; } @end @@ -25,33 +24,33 @@ return nil; } - _tuningParameters = std::vector(ASLayoutRangeTypeCount); - _tuningParameters[ASLayoutRangeTypeVisible] = { + _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); + + _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeVisible] = { .leadingBufferScreenfuls = 0, .trailingBufferScreenfuls = 0 }; - _tuningParameters[ASLayoutRangeTypeDisplay] = { - .leadingBufferScreenfuls = 1.5, - .trailingBufferScreenfuls = 0.75 - }; - _tuningParameters[ASLayoutRangeTypeFetchData] = { - .leadingBufferScreenfuls = 3, - .trailingBufferScreenfuls = 2 - }; - - _minimumTuningParameters = std::vector(ASLayoutRangeTypeCount); - _minimumTuningParameters[ASLayoutRangeTypeVisible] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; - _minimumTuningParameters[ASLayoutRangeTypeDisplay] = { + _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeDisplay] = { .leadingBufferScreenfuls = 0.25, .trailingBufferScreenfuls = 0.25 }; - _minimumTuningParameters[ASLayoutRangeTypeFetchData] = { + _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeFetchData] = { .leadingBufferScreenfuls = 1, .trailingBufferScreenfuls = 1 }; + + _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeVisible] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 + }; + _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeDisplay] = { + .leadingBufferScreenfuls = 1.5, + .trailingBufferScreenfuls = 0.75 + }; + _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeFetchData] = { + .leadingBufferScreenfuls = 3, + .trailingBufferScreenfuls = 2 + }; return self; } @@ -60,33 +59,28 @@ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeType]; + return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)"); - _tuningParameters[rangeType] = tuningParameters; + return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } -- (ASRangeTuningParameters)minimumTuningParametersForRangeType:(ASLayoutRangeType)rangeType +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _minimumTuningParameters.size(), @"Requesting a range that is OOB for the configured minimum tuning parameters"); - return _minimumTuningParameters[rangeType]; + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), + @"Requesting a range that is OOB for the configured tuning parameters"); + return _tuningParameters[rangeMode][rangeType]; } -- (void)setMinimumTuningParameters:(ASRangeTuningParameters)minimumTuningParameters forRangeType:(ASLayoutRangeType)rangeType +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _minimumTuningParameters.size(), @"Requesting a range that is OOB for the configured minimum tuning parameters"); - ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range minimum tuning parameters (always 0, 0)"); - _minimumTuningParameters[rangeType] = minimumTuningParameters; -} - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType isFullRange:(BOOL)isFullRange -{ - return isFullRange ? [self tuningParametersForRangeType:rangeType] : [self minimumTuningParametersForRangeType:rangeType]; + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), + @"Setting a range that is OOB for the configured tuning parameters"); + ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, + @"Must not set Visible range minimum tuning parameters (always 0, 0)"); + _tuningParameters[rangeMode][rangeType] = tuningParameters; } #pragma mark - Abstract Index Path Range Support @@ -98,7 +92,7 @@ return NO; } -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertNotSupported(); return nil; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index 6777c2d3de..bf591b2d93 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -66,9 +66,9 @@ typedef struct ASRangeGeometry ASRangeGeometry; return self; } -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection tuningParameters:tuningParameters]; _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds]; @@ -133,9 +133,9 @@ typedef struct ASRangeGeometry ASRangeGeometry; @implementation ASCollectionViewLayoutControllerBeta -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; return [self indexPathsForItemsWithinRangeBounds:rangeBounds]; } diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index d2b3a62bdc..cbcde0f011 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -74,7 +74,7 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; * IndexPath array for the element in the working range. */ -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { CGFloat viewportScreenMetric; ASScrollDirection leadingDirection; @@ -92,7 +92,7 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; leadingDirection = ASScrollDirectionUp; } - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType isFullRange:shouldUseFullRange]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; CGFloat backScreens = scrollDirection == leadingDirection ? tuningParameters.leadingBufferScreenfuls : tuningParameters.trailingBufferScreenfuls; CGFloat frontScreens = scrollDirection == leadingDirection ? tuningParameters.trailingBufferScreenfuls : tuningParameters.leadingBufferScreenfuls; diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 1a3d89f263..cdf205dbe9 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -16,6 +16,12 @@ NS_ASSUME_NONNULL_BEGIN @class ASCellNode; +typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { + ASLayoutRangeModeMinimum = 0, + ASLayoutRangeModeFull, + ASLayoutRangeModeCount +}; + typedef struct { CGFloat leadingBufferScreenfuls; CGFloat trailingBufferScreenfuls; @@ -24,23 +30,30 @@ typedef struct { @protocol ASLayoutController /** - * Tuning parameters for the range. + * Tuning parameters for the range type in full mode. This method is deprecated. + * Instead, use -setTuningParameters:forRangeMode:rangeType: + * + * @see setTuningParameters:forRangeMode:rangeType: */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED; -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; +/** + * Get tuning parameters for the range type in full mode. This method is deprecated. + * Instead, use -tuningParametersForRangeMode:rangeType: + * + * @see tuningParametersForRangeMode:rangeType: + */ +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED; -- (void)setMinimumTuningParameters:(ASRangeTuningParameters)minimumTuningParameters forRangeType:(ASLayoutRangeType)rangeType; +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; -- (ASRangeTuningParameters)minimumTuningParametersForRangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType isFullRange:(BOOL)isFullRange; +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; // FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. // TODO: Now that it is the main version, can we remove this now? - (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType; -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType shouldUseFullRange:(BOOL)shouldUseFullRange; +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; @optional diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 99a6492290..b728916a62 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -153,7 +153,9 @@ id rangeHandler = _rangeTypeHandlers[rangeKey]; if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths rangeType:rangeType]) { - NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:rangeType shouldUseFullRange:YES]; + NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:ASLayoutRangeModeFull + rangeType:rangeType]; // Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths NSMutableSet *removedIndexPaths = _rangeIsValid ? [_rangeTypeIndexPaths[rangeKey] mutableCopy] : [NSMutableSet set]; diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index a8d77c2301..63b92c3fe7 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -10,6 +10,7 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" +#import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" #import "ASRangeHandlerVisible.h" #import "ASRangeHandlerRender.h" @@ -17,12 +18,6 @@ #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" -typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { - ASRangeTypeUsedNone, - ASRangeTypeUsedMinimum, - ASRangeTypeUsedFull, -}; - @interface ASRangeControllerBeta () { BOOL _rangeIsValid; @@ -30,7 +25,8 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { BOOL _layoutControllerImplementsSetVisibleIndexPaths; ASScrollDirection _scrollDirection; NSSet *_allPreviousIndexPaths; - ASRangeTypeUsed _rangeTypeUsed; + ASLayoutRangeMode _currentRangeMode; + BOOL _didRegisterForNotifications; } @end @@ -44,13 +40,41 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { } _rangeIsValid = YES; - _rangeTypeUsed = ASRangeTypeUsedNone; + _currentRangeMode = ASLayoutRangeModeCount; return self; } +- (void)dealloc +{ + if (_didRegisterForNotifications) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } +} + #pragma mark - Core visible node range managment API ++ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState + scrollDirection:(ASScrollDirection)scrollDirection + currentRangeMode:(ASLayoutRangeMode)currentRangeMode +{ + // If we used full mode, don't switch to minimum mode. That will destroy all the hard work done before. + if (currentRangeMode == ASLayoutRangeModeFull) { + return ASLayoutRangeModeFull; + } + + BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); + BOOL isScrolling = (scrollDirection != ASScrollDirectionNone); + BOOL isUsingMinimumRangeMode = (currentRangeMode == ASLayoutRangeModeMinimum); + // If we are already visible and scrolling, get busy! Better get started on preloading before the user scrolls more... + // If we are already visible and finished displaying minimum mode, extend to full mode + if (isVisible && (isScrolling || isUsingMinimumRangeMode)) { + return ASLayoutRangeModeFull; + } + + return ASLayoutRangeModeMinimum; +} + - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection { _scrollDirection = scrollDirection; @@ -62,7 +86,7 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { if (_queuedRangeUpdate) { return; } - + // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; @@ -117,25 +141,25 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - BOOL selfIsVisible = (ASInterfaceStateIncludesVisible(selfInterfaceState)); - BOOL selfIsScrolling = (_scrollDirection != ASScrollDirectionNone); - BOOL didUseMinimumRange = (_rangeTypeUsed == ASRangeTypeUsedMinimum); - BOOL didUseFullRange = (_rangeTypeUsed == ASRangeTypeUsedFull); - // If we are already visible and scrolling, get busy! Better get started on preloading before the user scrolls more... - // If we are already visible and did finish displaying minimum range, extend to full range - // If we used full range, don't switch to minimum range now. That will destroy all the hard work done before. - BOOL useFullRange = ((selfIsVisible && (selfIsScrolling || didUseMinimumRange)) || didUseFullRange); - NSLog(@"%@ range: %@", useFullRange ? @"Full" : @"Minimum", [((ASCollectionView *)_delegate).asyncDelegate description]); + ASLayoutRangeMode rangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:selfInterfaceState + scrollDirection:_scrollDirection + currentRangeMode:_currentRangeMode]; - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData shouldUseFullRange:useFullRange]; + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay isFullRange:useFullRange]; - ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData isFullRange:useFullRange]; + ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode + rangeType:ASLayoutRangeTypeDisplay]; + ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls && parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) { displayIndexPaths = fetchDataIndexPaths; } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay shouldUseFullRange:useFullRange]; + displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:rangeMode + rangeType:ASLayoutRangeTypeDisplay]; } // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. @@ -151,11 +175,13 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; [allIndexPaths unionSet:_allPreviousIndexPaths]; _allPreviousIndexPaths = allCurrentIndexPaths; - _rangeTypeUsed = useFullRange ? ASRangeTypeUsedFull : ASRangeTypeUsedMinimum; + _currentRangeMode = rangeMode; if (!_rangeIsValid) { [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; } + + [self registerForNotificationsIfNeeded]; // This array is only used if logging is enabled. NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); @@ -240,6 +266,33 @@ typedef NS_ENUM(NSUInteger, ASRangeTypeUsed) { #endif } +#pragma mark - Notification observers + +- (void)registerForNotificationsIfNeeded +{ + if (!_didRegisterForNotifications) { + BOOL selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; + ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:selfInterfaceState + scrollDirection:_scrollDirection + currentRangeMode:_currentRangeMode]; + if (_currentRangeMode != nextRangeMode) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scheduledNodesDidDisplay) + name:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil]; + _didRegisterForNotifications = YES; + } + } +} + +- (void)scheduledNodesDidDisplay +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + _didRegisterForNotifications = NO; + + [self scheduleRangeUpdate]; +} + #pragma mark - Cell node view handling - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index e0740fd3fc..7bb47cf8a0 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -37,6 +37,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) @class _ASPendingState; @class _ASDisplayNodePosition; +FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; + // Allow 2^n increments of begin disabling hierarchy notifications #define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4 From 12e2b535dbe1fdded3b799fa90d9ba86a6679dea Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 2 Feb 2016 08:30:52 -0800 Subject: [PATCH 036/224] Use instance variable for tracking if a target was added to ASControlNode --- AsyncDisplayKit/ASControlNode+Subclasses.h | 6 ------ AsyncDisplayKit/ASControlNode.m | 7 +++---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode+Subclasses.h b/AsyncDisplayKit/ASControlNode+Subclasses.h index 9257911150..107de031e0 100644 --- a/AsyncDisplayKit/ASControlNode+Subclasses.h +++ b/AsyncDisplayKit/ASControlNode+Subclasses.h @@ -19,12 +19,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ASControlNode (Subclassing) -/** - @abstract Indicates whether or not at least one target was added to the receiver - @discussion YES if the receiver has at least one target; NO otherwise. - */ -@property (nonatomic, readonly, assign, getter=isTargetAdded) BOOL targetAdded; - /** @abstract Sends action messages for the given control events. @param controlEvents A bitmask whose set flags specify the control events for which action messages are sent. See "Control Events" in ASControlNode.h for bitmask constants. diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index fa6c6f3914..3ccb2ef7bd 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -48,7 +48,6 @@ } // Read-write overrides. -@property (nonatomic, readwrite, assign, getter=isTargetAdded) BOOL targetAdded; @property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; @@ -89,14 +88,14 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [super didLoad]; // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - if (!self.targetAdded) { + if (!_targetAdded) { self.userInteractionEnabled = NO; } } - (BOOL)shouldTrackTouches { - return self.isTargetAdded && self.enabled; + return _targetAdded && self.enabled; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -270,7 +269,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [targetActions addObject:NSStringFromSelector(action)]; }); - self.targetAdded = YES; + _targetAdded = YES; self.userInteractionEnabled = YES; } From 19335d8c9e5ebee0767f2e03021f396f5e1aacc6 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 2 Feb 2016 11:58:18 -0800 Subject: [PATCH 037/224] Add rangeMode APIs --- AsyncDisplayKit/ASCollectionNode.h | 38 ++++++++++++++--- AsyncDisplayKit/ASCollectionNode.mm | 14 ++++++- AsyncDisplayKit/ASCollectionView.h | 38 ++++++++++++++--- AsyncDisplayKit/ASCollectionView.mm | 9 ++-- AsyncDisplayKit/ASTableView.h | 44 ++++++++++++++++---- AsyncDisplayKit/ASTableView.mm | 12 +++--- AsyncDisplayKit/Details/ASLayoutController.h | 22 ---------- AsyncDisplayKit/Details/ASLayoutRangeType.h | 15 +++++++ AsyncDisplayKit/Details/ASRangeController.h | 5 ++- AsyncDisplayKit/Details/ASRangeController.mm | 8 ++-- 10 files changed, 146 insertions(+), 59 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 1b14087356..49ca3ffe5a 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -27,25 +27,53 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) ASCollectionView *view; /** - * Tuning parameters for a range type. + * Tuning parameters for a range type in full mode. * * @param rangeType The range type to get the tuning parameters for. * - * @returns A tuning parameter value for the given range type. + * @returns A tuning parameter value for the given range type in full mode. * - * Defaults to the render range having one sceenful both leading and trailing and the preload range having two - * screenfuls in both directions. + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range type. + * Set the tuning parameters for a range type in full mode. * * @param tuningParameters The tuning parameters to store for a range type. * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; +/** + * Tuning parameters for a range type in the specified mode. + * + * @param rangeMode The range mode to get the runing parameters for. + * @param rangeType The range type to get the tuning parameters for. + * + * @returns A tuning parameter value for the given range type in the given mode. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + +/** + * Set the tuning parameters for a range type in the specigied mode. + * + * @param tuningParameters The tuning parameters to store for a range type. + * @param rangeMode The range mode to set the runing parameters for. + * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + /** * Reload everything from scratch, destroying the working range and all cached nodes. * diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 3d5c3d9288..68e7d9a341 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -201,12 +201,22 @@ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [self.view.rangeController tuningParametersForRangeType:rangeType]; + return [self.view.rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - return [self.view.rangeController setTuningParameters:tuningParameters forRangeType:rangeType]; + [self.view.rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; +} + +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType +{ + return [self.view.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType +{ + return [self.view.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } - (void)reloadDataWithCompletion:(void (^)())completion diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 418d22c2d3..8a4a597f96 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -52,25 +52,53 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id asyncDataSource; /** - * Tuning parameters for a range type. + * Tuning parameters for a range type in full mode. * * @param rangeType The range type to get the tuning parameters for. * - * @returns A tuning parameter value for the given range type. + * @returns A tuning parameter value for the given range type in full mode. * - * Defaults to the render range having one sceenful both leading and trailing and the preload range having two - * screenfuls in both directions. + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range type. + * Set the tuning parameters for a range type in full mode. * * @param tuningParameters The tuning parameters to store for a range type. * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; +/** + * Tuning parameters for a range type in the specified mode. + * + * @param rangeMode The range mode to get the runing parameters for. + * @param rangeType The range type to get the tuning parameters for. + * + * @returns A tuning parameter value for the given range type in the given mode. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + +/** + * Set the tuning parameters for a range type in the specigied mode. + * + * @param tuningParameters The tuning parameters to store for a range type. + * @param rangeMode The range mode to set the runing parameters for. + * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + /** * The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index d8019a3e08..8d7e6fae76 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -347,15 +347,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [_collectionNode tuningParametersForRangeType:rangeType]; } -// These deprecated methods harken back from a time where only one range type existed. -- (ASRangeTuningParameters)rangeTuningParameters +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; + [_collectionNode setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } -- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; + return [_collectionNode tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 2a4d29b95a..bb7fa42062 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -56,25 +56,53 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; /** - * Tuning parameters for a range. + * Tuning parameters for a range type in full mode. * - * @param rangeType The range to get the tuning parameters for. + * @param rangeType The range type to get the tuning parameters for. * - * @returns A tuning parameter value for the given range. + * @returns A tuning parameter value for the given range type in full mode. * - * Defaults to the render range having one sceenful both leading and trailing and the preload range having two - * screenfuls in both directions. + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; /** - * Set the tuning parameters for a range. + * Set the tuning parameters for a range type in full mode. * - * @param tuningParameters The tuning parameters to store for a range. - * @param rangeType The range to set the tuning parameters for. + * @param tuningParameters The tuning parameters to store for a range type. + * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; +/** + * Tuning parameters for a range type in the specified mode. + * + * @param rangeMode The range mode to get the runing parameters for. + * @param rangeType The range type to get the tuning parameters for. + * + * @returns A tuning parameter value for the given range type in the given mode. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + +/** + * Set the tuning parameters for a range type in the specigied mode. + * + * @param tuningParameters The tuning parameters to store for a range type. + * @param rangeMode The range mode to set the runing parameters for. + * @param rangeType The range type to set the tuning parameters for. + * + * @see ASLayoutRangeMode + * @see ASLayoutRangeType + */ +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + /** * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 3f2bfb1990..a327f7a945 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -320,22 +320,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - [_layoutController setTuningParameters:tuningParameters forRangeType:rangeType]; + [_layoutController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [_layoutController tuningParametersForRangeType:rangeType]; + return [_layoutController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } -- (ASRangeTuningParameters)rangeTuningParameters +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; + [_layoutController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } -- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; + return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } - (NSArray *> *)completedNodes diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index cdf205dbe9..92a2f680d2 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -16,12 +16,6 @@ NS_ASSUME_NONNULL_BEGIN @class ASCellNode; -typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { - ASLayoutRangeModeMinimum = 0, - ASLayoutRangeModeFull, - ASLayoutRangeModeCount -}; - typedef struct { CGFloat leadingBufferScreenfuls; CGFloat trailingBufferScreenfuls; @@ -29,22 +23,6 @@ typedef struct { @protocol ASLayoutController -/** - * Tuning parameters for the range type in full mode. This method is deprecated. - * Instead, use -setTuningParameters:forRangeMode:rangeType: - * - * @see setTuningParameters:forRangeMode:rangeType: - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED; - -/** - * Get tuning parameters for the range type in full mode. This method is deprecated. - * Instead, use -tuningParametersForRangeMode:rangeType: - * - * @see tuningParametersForRangeMode:rangeType: - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED; - - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index d25817d962..33391f0cb3 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -8,6 +8,21 @@ #import +/// Each mode has a complete set of tuning parameters for range types. +/// Depends on some conditions (including interface state and direction of the scroll view, state of rendering engine, etc), +/// a range controller can choose which mode it should use at a given time. +typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { + /// Minimum mode is used when a range controller should limit the amount of work it performs. + /// Thus, less views/layers are created and less data is fetched. + /// Range controller can automatically switch to full mode when conditions changed. + ASLayoutRangeModeMinimum = 0, + /// Normal/Full mode that a range controller uses to provide the best experience for end users. + /// This mode is usually used for an active scroll view. + /// A range controller under this requires more resources compare to minimum mode. + ASLayoutRangeModeFull, + ASLayoutRangeModeCount +}; + typedef NS_ENUM(NSInteger, ASLayoutRangeType) { ASLayoutRangeTypeVisible = 0, ASLayoutRangeTypeDisplay, diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index 74061f8806..6a4fde0b23 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -53,8 +53,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; + +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; /** * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index b728916a62..8e9c56cf1d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -28,14 +28,14 @@ { } -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - [_layoutController setTuningParameters:tuningParameters forRangeType:rangeType]; + [_layoutController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - return [_layoutController tuningParametersForRangeType:rangeType]; + return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } @end From 1b08114eca435802665844eae33f3a2b98d774d7 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 2 Feb 2016 19:10:12 -0800 Subject: [PATCH 038/224] Allow prevention of downscaling --- AsyncDisplayKit/ASImageNode.h | 8 ++++++++ AsyncDisplayKit/ASImageNode.mm | 17 +++++++++++++++++ AsyncDisplayKit/Private/ASImageNode+CGExtras.h | 2 ++ AsyncDisplayKit/Private/ASImageNode+CGExtras.m | 3 ++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index 5ebc0113b8..0012350331 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -48,6 +48,14 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); */ @property (nonatomic, assign, getter=isCropEnabled) BOOL cropEnabled; +/** + * @abstract Indicates that efficient downsizing of backing store should *not* be enabled. + * + * @discussion Defaults to NO. @see ASCroppedImageBackingSizeAndDrawRectInBounds for more + * information. + */ +@property (nonatomic, assign) BOOL forceUpscaling; + /** * @abstract Enables or disables efficient cropping. * diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index c3d5d330a5..2824a2e81f 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -66,6 +66,7 @@ // Cropping. BOOL _cropEnabled; // Defaults to YES. + BOOL _forceUpscaling; //Defaults to NO. CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) CGRect _cropDisplayBounds; } @@ -84,6 +85,7 @@ self.opaque = NO; _cropEnabled = YES; + _forceUpscaling = NO; _cropRect = CGRectMake(0.5, 0.5, 0, 0); _cropDisplayBounds = CGRectNull; _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); @@ -156,6 +158,7 @@ { UIImage *image; BOOL cropEnabled; + BOOL forceUpscaling; CGFloat contentsScale; CGRect cropDisplayBounds; CGRect cropRect; @@ -169,6 +172,7 @@ } cropEnabled = _cropEnabled; + forceUpscaling = _forceUpscaling; contentsScale = _contentsScaleForDisplay; cropDisplayBounds = _cropDisplayBounds; cropRect = _cropRect; @@ -223,6 +227,7 @@ boundsSizeInPixels, contentMode, cropRect, + forceUpscaling, &backingSize, &imageDrawRect); } @@ -385,6 +390,18 @@ }); } +- (BOOL)forceUpscaling +{ + ASDN::MutexLocker l(_imageLock); + return _forceUpscaling; +} + +- (void)setForceUpscaling:(BOOL)forceUpscaling +{ + ASDN::MutexLocker l(_imageLock); + _forceUpscaling = forceUpscaling; +} + - (asimagenode_modification_block_t)imageModificationBlock { ASDN::MutexLocker l(_imageLock); diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h index e549313c8a..0c4803c206 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h @@ -19,12 +19,14 @@ ASDISPLAYNODE_EXTERN_C_BEGIN @param boundsSize The bounds in which the image will be displayed. @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using percentages of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. + @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. */ extern void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, CGSize boundsSize, UIViewContentMode contentMode, CGRect cropRect, + BOOL forceUpscaling, CGSize *outBackingSize, CGRect *outDrawRect ); diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m index a3d862700b..1467bc0e14 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m @@ -36,6 +36,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, CGSize boundsSize, UIViewContentMode contentMode, CGRect cropRect, + BOOL forceUpscaling, CGSize *outBackingSize, CGRect *outDrawRect ) @@ -62,7 +63,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. // However, if there is a pixel savings (e.g. we would have to upscale the image), overwrite the function arguments. - if ((scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { + if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { destinationWidth = (size_t)roundf(scaledSizeForImage.width); destinationHeight = (size_t)roundf(scaledSizeForImage.height); if (destinationWidth == 0 || destinationHeight == 0) { From 597aa02c8ecf6391ed81c704e7fb90da9fcb0bca Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 3 Feb 2016 18:53:20 -0800 Subject: [PATCH 039/224] Remove target added state and and instead check _controlEventDispatchTable if a target was added --- AsyncDisplayKit/ASControlNode.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 3ccb2ef7bd..4356c66906 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -23,7 +23,6 @@ { @private // Control Attributes - BOOL _targetAdded; BOOL _enabled; BOOL _highlighted; @@ -88,14 +87,14 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [super didLoad]; // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - if (!_targetAdded) { + if (![self hasTarget]) { self.userInteractionEnabled = NO; } } - (BOOL)shouldTrackTouches { - return _targetAdded && self.enabled; + return [self hasTarget] && self.enabled; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -269,7 +268,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [targetActions addObject:NSStringFromSelector(action)]; }); - _targetAdded = YES; self.userInteractionEnabled = YES; } @@ -351,9 +349,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v else removeActionFromTarget(target, action); }); + + self.userInteractionEnabled = [self hasTarget]; } #pragma mark - +- (BOOL)hasTarget +{ + return (_controlEventDispatchTable.count > 0); +} + - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event { NSParameterAssert(controlEvents != 0); From cd94df11066a783ccca11778bd95d6e873165c4e Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 3 Feb 2016 20:08:30 -0800 Subject: [PATCH 040/224] Wrap implicit hierarchy management behind beta feature flag --- AsyncDisplayKit/ASDisplayNode.mm | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7d8e40c217..cc2f1b3670 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -644,17 +644,19 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; - if (_layout) { - NSIndexSet *insertions, *deletions; - [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { - return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); - }]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; - _deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; - } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; - _deletedSubnodes = @[]; + if ([[self class] usesImplicitHierarchyManagement]) { + if (_layout) { + NSIndexSet *insertions, *deletions; + [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); + }]; + _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; + _deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; + } else { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; + _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; + _deletedSubnodes = @[]; + } } _layout = newLayout; From 521c3fa1c1bb0246d141a7d604a0306e7557c5b2 Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Tue, 2 Feb 2016 15:00:24 -0800 Subject: [PATCH 041/224] Move ASCellNode allocation off the main thread by the addition of a node block API in ASDataController. Move allocations and loaded node layouts to occur during batch layout phase. --- AsyncDisplayKit/ASCollectionView.h | 15 +- AsyncDisplayKit/ASCollectionView.mm | 27 +++ AsyncDisplayKit/ASPagerNode.h | 6 + AsyncDisplayKit/ASPagerNode.m | 11 ++ AsyncDisplayKit/ASTableView.h | 16 +- AsyncDisplayKit/ASTableView.mm | 27 +++ .../Details/ASCollectionDataController.h | 4 + .../Details/ASCollectionDataController.mm | 29 ++- AsyncDisplayKit/Details/ASDataController.h | 12 ++ AsyncDisplayKit/Details/ASDataController.mm | 168 ++++++++++++++---- AsyncDisplayKit/Details/ASDelegateProxy.m | 11 +- ...ASCollectionViewFlowLayoutInspectorTests.m | 4 + AsyncDisplayKitTests/ASCollectionViewTests.m | 9 + AsyncDisplayKitTests/ASTableViewTests.m | 15 ++ 14 files changed, 306 insertions(+), 48 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 8a4a597f96..94ae05d3cb 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -347,14 +347,25 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). Unlike UICollectionView's version, this method + * @returns a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UICollectionView's version, this method * is not called when the row is about to display. */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; + @optional +/** + * + * @param collectionView The sender. + * + * @param indexPath The index path of the requested node. + * + * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). + */ +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath; + /** * Asks the collection view to provide a supplementary node to display in the collection view. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 8d7e6fae76..2b089cc936 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -690,6 +690,33 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return node; } + +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ + if (![_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockAtIndexPath:)]) { + ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + return ^{ + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; + } + + ASDataControllerCellNodeBlock block = [_asyncDataSource collectionView:self nodeBlockAtIndexPath:indexPath]; + ASDisplayNodeAssertNotNil(block, @"Invalid block, expected nonnull ASDataControllerCellNodeBlock"); + return ^{ + ASCellNode *node = block(); + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { ASSizeRange constrainedSize = kInvalidSizeRange; diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index fa992eb1b5..bdf792a33f 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -15,6 +15,12 @@ // This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +@optional + +// This method replaces -collectionView:nodeBlockForItemAtIndexPath: +- (ASDataControllerCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; + @end @interface ASPagerNode : ASCollectionNode diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index c7db4850ac..486520d267 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -84,6 +84,17 @@ return pageNode; } +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); + if (![_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]) { + ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; + return ^{ return node; }; + } + ASDataControllerCellNodeBlock block = [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; + ASDisplayNodeAssertNotNil(block, @"Invalid node block. Block should be non-nil."); + return block; +} + - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index bb7fa42062..c63ad12a64 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -322,14 +322,26 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method + * @returns a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method * is not called when the row is about to display. */ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; @optional +/** + * Similar to -tableView:nodeForRowAtIndexPath:. + * + * @param tableView The sender. + * + * @param indexPath The index path of the requested node. + * + * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). + */ + +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; + /** * Indicator to lock the data source for data fetching in async mode. * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index a327f7a945..58dfc135ab 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -876,6 +876,33 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return node; } +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + if (![_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]) { + ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; + return ^{ + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; + } + + ASDataControllerCellNodeBlock block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; + __weak __typeof__(self) weakSelf = self; + ASDataControllerCellNodeBlock configuredNodeBlock = ^{ + __typeof__(self) strongSelf = weakSelf; + ASCellNode *node = block(); + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + if (node.layoutDelegate == nil) { + node.layoutDelegate = strongSelf; + } + return node; + }; + return configuredNodeBlock; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { return ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 7c66ff41fb..01a3f9f694 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -30,6 +30,10 @@ - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; +@optional + +- (ASDataControllerCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + @end @interface ASCollectionDataController : ASChangeSetDataController diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 9e51ee6a24..adc1548155 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -24,8 +24,8 @@ @end @implementation ASCollectionDataController { - NSMutableDictionary *_pendingNodes; - NSMutableDictionary *_pendingIndexPaths; + NSMutableDictionary *> *_pendingNodes; + NSMutableDictionary *> *_pendingIndexPaths; } - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled @@ -49,7 +49,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -91,7 +91,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -132,7 +132,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -176,7 +176,14 @@ for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - [nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]]; + ASDataControllerCellNodeBlock supplementaryCellBlock; + if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + [nodes addObject:supplementaryCellBlock]; } } } @@ -189,8 +196,14 @@ for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - [nodes addObject:supplementaryNode]; + ASDataControllerCellNodeBlock supplementaryCellBlock; + if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + [nodes addObject:supplementaryCellBlock]; } }]; } diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 5710b22b4e..6630babd08 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -20,12 +20,19 @@ NS_ASSUME_NONNULL_BEGIN @class ASDataController; typedef NSUInteger ASDataControllerAnimationOptions; + +/** + * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. + */ +typedef ASCellNode * _Nonnull(^ASDataControllerCellNodeBlock)(); + FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; /** Data source for data controller It will be invoked in the same thread as the api call of ASDataController. */ + @protocol ASDataControllerSource /** @@ -33,6 +40,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath; +/** + Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. + */ +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; + /** The constrained size range for layout. */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 53b3715874..fa4006255d 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -39,10 +39,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. BOOL _asyncDataFetchingEnabled; + BOOL _delegateDidInsertNodes; BOOL _delegateDidDeleteNodes; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + + BOOL _dataSourceImplementsAsyncNodeAtIndexPath; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -94,6 +97,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; } +- (void)setDataSource:(id)dataSource { + if (_dataSource == dataSource) { + return; + } + + _dataSource = dataSource; + // This probably won't be sufficient to tell if we should call the node block + _dataSourceImplementsAsyncNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:nodeBlockAtIndexPath:)]; +} + + (NSUInteger)parallelProcessorCount { static NSUInteger parallelProcessorCount; @@ -108,7 +121,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; @@ -117,12 +130,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; } } -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { +- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { @@ -159,22 +171,50 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (!nodes.count) { return; } - + + NSUInteger nodeCount = nodes.count; + NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); - ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodes.count); + ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); + BOOL isMainThread = [NSThread isMainThread]; for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - - for (NSUInteger k = j; k < j + batchCount; k++) { - ASCellNode *node = nodes[k]; - if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + + + if (isMainThread) { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + for (NSUInteger k = j; k < j + batchCount; k++) { + ASDataControllerCellNodeBlock cellBlock = nodes[k]; + ASCellNode *node = cellBlock(); + ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); + [allocatedNodes addObject:node]; + if (!node.isNodeLoaded) { + nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + } + } + dispatch_semaphore_signal(sema); + }); + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + } else { + for (NSUInteger k = j; k < j + batchCount; k++) { + ASDataControllerCellNodeBlock cellBlock = nodes[k]; + ASCellNode *node = cellBlock(); + ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); + [allocatedNodes addObject:node]; + if (!node.isNodeLoaded) { + nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + } } + [_mainSerialQueue performBlockOnMainThread:^{ + [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + }]; } - + dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSUInteger k = j; k < j + batchCount; k++) { - ASCellNode *node = nodes[k]; + ASCellNode *node = allocatedNodes[k]; // Only measure nodes whose views aren't loaded, since we're in the background. // We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths: if (!node.isNodeLoaded) { @@ -183,13 +223,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } }); } - + // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); free(nodeBoundSizes); if (completionBlock) { - completionBlock(nodes, indexPaths); + completionBlock(allocatedNodes, indexPaths); } } @@ -382,9 +422,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; + +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -409,9 +451,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } + if (completion) { dispatch_async(dispatch_get_main_queue(), completion); } @@ -462,12 +517,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } }]; } @@ -482,12 +540,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; for (NSUInteger i = 0; i < sectionNum; i++) { NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; for (NSUInteger j = 0; j < rowNum; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } } } @@ -578,8 +639,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } [self prepareForInsertSections:sections]; @@ -591,9 +654,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } }]; }]; }]; @@ -635,9 +711,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Dispatch to sizing queue in order to guarantee that any in-progress sizing operations from prior edits have completed. // For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done // at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...] - - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } [self prepareForReloadSections:sections]; @@ -649,9 +726,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - + // reinsert the elements [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } }]; }]; }]; @@ -747,12 +837,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:sortedIndexPaths[i]]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + } } // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; - +// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; @@ -797,11 +891,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; +// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 6f3594ecc8..bf391db103 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -81,8 +81,9 @@ @end @implementation ASDelegateProxy { - id __weak _target; id __weak _interceptor; +@protected + NSObject * __weak _target; } - (instancetype)initWithTarget:(id )target interceptor:(id )interceptor @@ -130,4 +131,12 @@ return NO; } +- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + if ([_target isEqual:[NSNull null]]) { + return nil; + } + return [_target methodSignatureForSelector:selector]; +} + @end diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index a9c5614e5f..76b9cc4674 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -25,6 +25,10 @@ return [[ASCellNode alloc] init]; } +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + return ^{ return [[ASCellNode alloc] init]; }; +} + - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 0; diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 6059fabad6..6c4be4ec2c 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -35,6 +35,15 @@ return textCellNode; } + +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + return ^{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + return textCellNode; + }; +} + - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return self.numberOfSections; } diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 676634e106..c9f581df0e 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -72,6 +72,11 @@ return nil; } +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return nil; +} + - (void)dealloc { if (_willDeallocBlock) { @@ -121,6 +126,16 @@ return textCellNode; } + +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return ^{ + ASTestTextCellNode *textCellNode = [ASTestTextCellNode new]; + textCellNode.text = indexPath.description; + return textCellNode; + }; +} + @end @interface ASTableViewTests : XCTestCase From 3c135788cb64da332e2d592c0de59fe8a5c9f629 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 3 Feb 2016 14:24:44 -0800 Subject: [PATCH 042/224] Less work when RangeControllerLoggingEnabled is false --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 63b92c3fe7..42d082e6a3 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -183,8 +183,9 @@ [self registerForNotificationsIfNeeded]; - // This array is only used if logging is enabled. +#if RangeControllerLoggingEnabled NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); +#endif for (NSIndexPath *indexPath in allIndexPaths) { // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. @@ -234,7 +235,9 @@ ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. if (node.interfaceState != interfaceState) { +#if RangeControllerLoggingEnabled [modifiedIndexPaths addObject:indexPath]; +#endif [node recursivelySetInterfaceState:interfaceState]; } } From fda9efafa65556e4ad2e8e5adeb77468703358da Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 13:59:43 -0800 Subject: [PATCH 043/224] Add timestamp to notifications of rendering engine and avoid race conditions in ASRangeControllerBeta - Accurately remove notification observer --- AsyncDisplayKit/ASDisplayNode.mm | 5 +++- .../Details/ASRangeControllerBeta.mm | 25 +++++++++++++------ .../Private/ASDisplayNodeInternal.h | 1 + 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index cc2f1b3670..a5d33641c8 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -30,6 +30,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; +NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; @interface _ASDisplayNodePosition : NSObject @@ -276,12 +277,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(displaySchedulerLock); displayScheduled = NO; NSSet *displayingNodes = [nodesToDisplay copy]; + CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent(); nodesToDisplay = nil; for (ASDisplayNode *node in displayingNodes) { [node __recursivelyTriggerDisplayAndBlock:NO]; } [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil]; + object:displayingNodes + userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: [NSNumber numberWithDouble:timestamp]}]; }); } } diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 42d082e6a3..e0b7fef2a1 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -27,6 +27,7 @@ NSSet *_allPreviousIndexPaths; ASLayoutRangeMode _currentRangeMode; BOOL _didRegisterForNotifications; + CFAbsoluteTime _pendingDisplayNodesTimestamp; } @end @@ -48,7 +49,7 @@ - (void)dealloc { if (_didRegisterForNotifications) { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; } } @@ -243,7 +244,11 @@ } } } - + + if (_didRegisterForNotifications) { + _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); + } + _rangeIsValid = YES; _queuedRangeUpdate = NO; @@ -280,7 +285,7 @@ currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scheduledNodesDidDisplay) + selector:@selector(scheduledNodesDidDisplay:) name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; _didRegisterForNotifications = YES; @@ -288,12 +293,16 @@ } } -- (void)scheduledNodesDidDisplay +- (void)scheduledNodesDidDisplay:(NSNotification *)notification { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - _didRegisterForNotifications = NO; - - [self scheduleRangeUpdate]; + CFAbsoluteTime notificationTimestamp = ((NSNumber *)[notification.userInfo objectForKey:ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; + if (_pendingDisplayNodesTimestamp < notificationTimestamp) { + // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update + [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; + _didRegisterForNotifications = NO; + + [self scheduleRangeUpdate]; + } } #pragma mark - Cell node view handling diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 7bb47cf8a0..6b5b4eb484 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -38,6 +38,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) @class _ASDisplayNodePosition; FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; +FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp; // Allow 2^n increments of begin disabling hierarchy notifications #define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4 From 4cf0e3e3807beeeb1ff2fe28246b58a1a26f2acc Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:00:29 -0800 Subject: [PATCH 044/224] Update range tunining params for ASPagerNode --- AsyncDisplayKit/ASPagerNode.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index c7db4850ac..cbace7c131 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -60,11 +60,16 @@ // our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning. // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). cv.zeroContentInsets = YES; + + ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; + ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; + [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; + [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeFetchData]; - ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; - ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - [self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypeFetchData]; - [self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeDisplay]; + ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; + ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; + [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; + [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData]; } #pragma mark - Helpers From 175b9da251484fc4ad3618a8899d3d13b2b22d04 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:01:14 -0800 Subject: [PATCH 045/224] Remove visible range type --- AsyncDisplayKit/Details/ASAbstractLayoutController.mm | 10 ---------- AsyncDisplayKit/Details/ASLayoutRangeType.h | 1 - 2 files changed, 11 deletions(-) diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 7b1f107413..d5696654ca 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -26,10 +26,6 @@ _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); - _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeVisible] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; _tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeDisplay] = { .leadingBufferScreenfuls = 0.25, .trailingBufferScreenfuls = 0.25 @@ -39,10 +35,6 @@ .trailingBufferScreenfuls = 1 }; - _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeVisible] = { - .leadingBufferScreenfuls = 0, - .trailingBufferScreenfuls = 0 - }; _tuningParameters[ASLayoutRangeModeFull][ASLayoutRangeTypeDisplay] = { .leadingBufferScreenfuls = 1.5, .trailingBufferScreenfuls = 0.75 @@ -78,8 +70,6 @@ { ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, - @"Must not set Visible range minimum tuning parameters (always 0, 0)"); _tuningParameters[rangeMode][rangeType] = tuningParameters; } diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index 33391f0cb3..8f28847827 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -24,7 +24,6 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { }; typedef NS_ENUM(NSInteger, ASLayoutRangeType) { - ASLayoutRangeTypeVisible = 0, ASLayoutRangeTypeDisplay, ASLayoutRangeTypeFetchData, ASLayoutRangeTypeCount From b3b28b0df9a8e4d5c708ce443b78aaf34ef443f0 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:03:08 -0800 Subject: [PATCH 046/224] Reuse visible index paths of when tuning params of a certain range type is zero --- .../Details/ASAbstractLayoutController.mm | 7 +++++++ AsyncDisplayKit/Details/ASLayoutController.h | 4 ++++ .../Details/ASRangeControllerBeta.mm | 19 ++++++++++++------- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index d5696654ca..2edcd4a78f 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -10,6 +10,13 @@ #import "ASAssert.h" #include +extern ASRangeTuningParameters const ASRangeTuningParametersZero = {}; + +extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs) +{ + return lhs.leadingBufferScreenfuls == rhs.leadingBufferScreenfuls && lhs.trailingBufferScreenfuls == rhs.trailingBufferScreenfuls; +} + @interface ASAbstractLayoutController () { std::vector> _tuningParameters; CGSize _viewportSize; diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 92a2f680d2..0ed8658876 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -21,6 +21,10 @@ typedef struct { CGFloat trailingBufferScreenfuls; } ASRangeTuningParameters; +FOUNDATION_EXPORT ASRangeTuningParameters const ASRangeTuningParametersZero; + +FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningParameters lhs, ASRangeTuningParameters rhs); + @protocol ASLayoutController - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index e0b7fef2a1..cbb0b66fe3 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -146,16 +146,21 @@ scrollDirection:_scrollDirection currentRangeMode:_currentRangeMode]; - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - rangeMode:rangeMode - rangeType:ASLayoutRangeTypeFetchData]; + ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; + if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersFetchData, ASRangeTuningParametersZero)) { + fetchDataIndexPaths = visibleIndexPaths; + } else { + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; + } ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; - ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypeFetchData]; - if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls && - parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) { + if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { + displayIndexPaths = visibleIndexPaths; + } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { displayIndexPaths = fetchDataIndexPaths; } else { displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection From 260879db7d30cdbe3747ef1138e680fc70f2dbf8 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:03:37 -0800 Subject: [PATCH 047/224] Improve documentation of ASLayoutRangeMode --- AsyncDisplayKit/Details/ASLayoutRangeType.h | 24 +++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index 8f28847827..7be3123533 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -8,17 +8,23 @@ #import -/// Each mode has a complete set of tuning parameters for range types. -/// Depends on some conditions (including interface state and direction of the scroll view, state of rendering engine, etc), -/// a range controller can choose which mode it should use at a given time. +/** + * Each mode has a complete set of tuning parameters for range types. + * Depending on some conditions (including interface state and direction of the scroll view, state of rendering engine, etc), + * a range controller can choose which mode it should use at a given time. + */ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { - /// Minimum mode is used when a range controller should limit the amount of work it performs. - /// Thus, less views/layers are created and less data is fetched. - /// Range controller can automatically switch to full mode when conditions changed. + /** + * Minimum mode is used when a range controller should limit the amount of work it performs. + * Thus, fewer views/layers are created and less data is fetched, saving system resources. + * Range controller can automatically switch to full mode when conditions change. + */ ASLayoutRangeModeMinimum = 0, - /// Normal/Full mode that a range controller uses to provide the best experience for end users. - /// This mode is usually used for an active scroll view. - /// A range controller under this requires more resources compare to minimum mode. + /** + * Normal/Full mode that a range controller uses to provide the best experience for end users. + * This mode is usually used for an active scroll view. + * A range controller under this requires more resources compare to minimum mode. + */ ASLayoutRangeModeFull, ASLayoutRangeModeCount }; From f09024556b7de9bb4ad1898b5663b836feefa8de Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:05:52 -0800 Subject: [PATCH 048/224] Switch to minimum mode when the node is no longer visible --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index cbb0b66fe3..d89bc3f7d4 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -59,11 +59,6 @@ scrollDirection:(ASScrollDirection)scrollDirection currentRangeMode:(ASLayoutRangeMode)currentRangeMode { - // If we used full mode, don't switch to minimum mode. That will destroy all the hard work done before. - if (currentRangeMode == ASLayoutRangeModeFull) { - return ASLayoutRangeModeFull; - } - BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); BOOL isScrolling = (scrollDirection != ASScrollDirectionNone); BOOL isUsingMinimumRangeMode = (currentRangeMode == ASLayoutRangeModeMinimum); From 8c83e1a78a727effd5024b79fa3f2842bfc30493 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 14:07:38 -0800 Subject: [PATCH 049/224] Avoid asking for interface state multiple times in ASRangeControllerBeta --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index d89bc3f7d4..e989d9b18c 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -182,7 +182,9 @@ [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; } - [self registerForNotificationsIfNeeded]; + // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. + // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings + [self registerForNotificationsIfNeededForInterfaceState:selfInterfaceState]; #if RangeControllerLoggingEnabled NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); @@ -276,11 +278,10 @@ #pragma mark - Notification observers -- (void)registerForNotificationsIfNeeded +- (void)registerForNotificationsIfNeededForInterfaceState:(ASInterfaceState)interfaceState { if (!_didRegisterForNotifications) { - BOOL selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:selfInterfaceState + ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:interfaceState scrollDirection:_scrollDirection currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { From dc93192b87e2b4a10e91694a4897eb5cb86997fe Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 15:04:12 -0800 Subject: [PATCH 050/224] Address Levi' comment: better method name in ASRangeControllerBeta --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index e989d9b18c..3557e00446 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -184,7 +184,7 @@ // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings - [self registerForNotificationsIfNeededForInterfaceState:selfInterfaceState]; + [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; #if RangeControllerLoggingEnabled NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); @@ -278,7 +278,7 @@ #pragma mark - Notification observers -- (void)registerForNotificationsIfNeededForInterfaceState:(ASInterfaceState)interfaceState +- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState { if (!_didRegisterForNotifications) { ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:interfaceState From e5e34313ed17c69438f97187492603c10ea59d72 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 15:25:43 -0800 Subject: [PATCH 051/224] ASRangeController (stable) doesn't handle visible range now because the range is gone --- AsyncDisplayKit/Details/ASRangeController.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 8e9c56cf1d..fbcddb2351 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -65,7 +65,6 @@ _rangeIsValid = YES; _rangeTypeIndexPaths = [NSMutableDictionary dictionary]; _rangeTypeHandlers = @{ - @(ASLayoutRangeTypeVisible) : [[ASRangeHandlerVisible alloc] init], @(ASLayoutRangeTypeDisplay) : [[ASRangeHandlerRender alloc] init], @(ASLayoutRangeTypeFetchData): [[ASRangeHandlerPreload alloc] init], }; From 79f745074226efa40e626a8fc210e6caccd9ab65 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 15:48:00 -0800 Subject: [PATCH 052/224] ASPagerNode less aggressively pre-renders --- AsyncDisplayKit/ASPagerNode.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index cbace7c131..6042bcf45a 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -61,12 +61,12 @@ // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). cv.zeroContentInsets = YES; - ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; + ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeFetchData]; - ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; + ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData]; From a7df20d1201be1859342af02bb855191c6eea674 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 16:51:21 -0800 Subject: [PATCH 053/224] Update range mode selection logic in ASRangeControllerBeta --- AsyncDisplayKit/Details/ASLayoutRangeType.h | 2 ++ AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 15 +++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index 7be3123533..aed3d98bb9 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -29,6 +29,8 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) { ASLayoutRangeModeCount }; +#define ASLayoutRangeModeInvalid ASLayoutRangeModeCount + typedef NS_ENUM(NSInteger, ASLayoutRangeType) { ASLayoutRangeTypeDisplay, ASLayoutRangeTypeFetchData, diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 3557e00446..82eb877917 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -41,7 +41,7 @@ } _rangeIsValid = YES; - _currentRangeMode = ASLayoutRangeModeCount; + _currentRangeMode = ASLayoutRangeModeInvalid; return self; } @@ -56,19 +56,15 @@ #pragma mark - Core visible node range managment API + (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState - scrollDirection:(ASScrollDirection)scrollDirection currentRangeMode:(ASLayoutRangeMode)currentRangeMode { BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); - BOOL isScrolling = (scrollDirection != ASScrollDirectionNone); - BOOL isUsingMinimumRangeMode = (currentRangeMode == ASLayoutRangeModeMinimum); - // If we are already visible and scrolling, get busy! Better get started on preloading before the user scrolls more... - // If we are already visible and finished displaying minimum mode, extend to full mode - if (isVisible && (isScrolling || isUsingMinimumRangeMode)) { - return ASLayoutRangeModeFull; + BOOL isFirstRangeUpdate = (currentRangeMode == ASLayoutRangeModeInvalid); + if (!isVisible || isFirstRangeUpdate) { + return ASLayoutRangeModeMinimum; } - return ASLayoutRangeModeMinimum; + return ASLayoutRangeModeFull; } - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection @@ -282,7 +278,6 @@ { if (!_didRegisterForNotifications) { ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:interfaceState - scrollDirection:_scrollDirection currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { [[NSNotificationCenter defaultCenter] addObserver:self From c4f489b074bc66b62924753044622fdabf57bdae Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Thu, 4 Feb 2016 14:29:50 -0800 Subject: [PATCH 054/224] Address comments. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 42 ++--- AsyncDisplayKit/ASCollectionView.h | 12 +- AsyncDisplayKit/ASCollectionView.mm | 24 ++- AsyncDisplayKit/ASPagerNode.h | 2 +- AsyncDisplayKit/ASPagerNode.m | 11 +- AsyncDisplayKit/ASTableView.h | 7 +- AsyncDisplayKit/ASTableView.mm | 12 +- .../Details/ASCollectionDataController.h | 2 +- .../Details/ASCollectionDataController.mm | 18 ++- AsyncDisplayKit/Details/ASDataController.h | 4 +- AsyncDisplayKit/Details/ASDataController.mm | 143 +++--------------- AsyncDisplayKit/Details/ASDelegateProxy.m | 11 +- ...ASCollectionViewFlowLayoutInspectorTests.m | 3 +- AsyncDisplayKitTests/ASCollectionViewTests.m | 2 +- AsyncDisplayKitTests/ASTableViewTests.m | 4 +- 15 files changed, 100 insertions(+), 197 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f577bfe88c..001681acc7 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -555,8 +555,8 @@ 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; sourceTree = ""; }; 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageNode.h; sourceTree = ""; }; 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageNode.mm; sourceTree = ""; }; - 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = ""; }; - 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableView.mm; sourceTree = ""; }; + 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASTableView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableView.mm; sourceTree = ""; }; 055F1A3619ABD413004DAFF1 /* ASRangeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeController.h; sourceTree = ""; }; 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeController.mm; sourceTree = ""; }; 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCellNode.h; sourceTree = ""; }; @@ -654,12 +654,12 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionDataController.h; sourceTree = ""; }; - 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionDataController.mm; sourceTree = ""; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = ""; }; - 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; }; + 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = ""; }; 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTests.mm; sourceTree = ""; }; 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEqualityHashHelpers.mm; sourceTree = ""; }; @@ -690,8 +690,8 @@ 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = ""; }; 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = ""; }; 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = ""; }; - 25E327541C16819500A2170C /* ASPagerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerNode.h; sourceTree = ""; }; - 25E327551C16819500A2170C /* ASPagerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerNode.m; sourceTree = ""; }; + 25E327541C16819500A2170C /* ASPagerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASPagerNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 25E327551C16819500A2170C /* ASPagerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASPagerNode.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = ""; }; @@ -705,11 +705,11 @@ 299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = ""; }; 299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = ""; }; 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; }; - 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = ""; }; + 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDataController.h; sourceTree = ""; }; - 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDataController.mm; sourceTree = ""; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; @@ -731,7 +731,7 @@ 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = ""; }; 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = ""; }; - 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; + 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = ""; }; A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitFontSizeAdjuster.m; path = TextKit/ASTextKitFontSizeAdjuster.m; sourceTree = ""; }; A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = ""; }; @@ -741,8 +741,8 @@ AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASHierarchyChangeSet.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; - AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; - AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionView.mm; sourceTree = ""; }; + AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionView.h; sourceTree = ""; }; + AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionView.mm; sourceTree = ""; }; AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewProtocols.h; sourceTree = ""; }; AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeSize.h; path = AsyncDisplayKit/Layout/ASRelativeSize.h; sourceTree = ""; }; AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeSize.mm; path = AsyncDisplayKit/Layout/ASRelativeSize.mm; sourceTree = ""; }; @@ -1629,7 +1629,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - FB42E06CF915B60406431170 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1759,21 +1758,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - FB42E06CF915B60406431170 /* 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 */ diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 94ae05d3cb..3e01e095a4 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -347,8 +347,9 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UICollectionView's version, this method - * is not called when the row is about to display. + * @returns a node for display at this indexpath. This will be called on the main thread and should + * not implement reuse (it will be called once per row). Unlike UICollectionView's version, + * this method is not called when the row is about to display. */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; @@ -361,10 +362,11 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). + * @returns a block that creates the node for display at this indexpath. + * Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). */ -- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath; +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; /** * Asks the collection view to provide a supplementary node to display in the collection view. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 2b089cc936..4a3f05c25b 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -77,6 +77,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; BOOL _asyncDelegateImplementsInsetSection; BOOL _collectionViewLayoutImplementsInsetSection; BOOL _asyncDataSourceImplementsConstrainedSizeForNode; + BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath; BOOL _queuedNodeSizeUpdate; BOOL _isDeallocating; @@ -301,10 +302,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _asyncDataSourceImplementsConstrainedSizeForNode = NO; + _asyncDataSourceImplementsNodeBlockForItemAtIndexPath = NO; } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0); + _asyncDataSourceImplementsConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + _asyncDataSourceImplementsNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; } super.dataSource = (id)_proxyDataSource; @@ -691,27 +694,32 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } -- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { - if (![_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockAtIndexPath:)]) { + if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + __weak __typeof__(self) weakSelf = self; return ^{ + __typeof__(self) strongSelf = weakSelf; [node enterHierarchyState:ASHierarchyStateRangeManaged]; - ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { - node.layoutDelegate = self; + node.layoutDelegate = strongSelf; } return node; }; } - ASDataControllerCellNodeBlock block = [_asyncDataSource collectionView:self nodeBlockAtIndexPath:indexPath]; - ASDisplayNodeAssertNotNil(block, @"Invalid block, expected nonnull ASDataControllerCellNodeBlock"); + ASCellNodeBlock block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; + ASDisplayNodeAssertNotNil(block, @"Invalid block, expected nonnull ASCellNodeBlock"); + __weak __typeof__(self) weakSelf = self; return ^{ + __typeof__(self) strongSelf = weakSelf; + ASCellNode *node = block(); [node enterHierarchyState:ASHierarchyStateRangeManaged]; if (node.layoutDelegate == nil) { - node.layoutDelegate = self; + node.layoutDelegate = strongSelf; } return node; }; diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index bdf792a33f..1b36e45bfd 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -19,7 +19,7 @@ @optional // This method replaces -collectionView:nodeBlockForItemAtIndexPath: -- (ASDataControllerCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; +- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; @end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 486520d267..d447f81779 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -16,6 +16,7 @@ UICollectionViewFlowLayout *_flowLayout; ASPagerNodeProxy *_proxy; __weak id _pagerDataSource; + BOOL _pagerDataSourceImplementsNodeBlockAtIndex; } @end @@ -84,15 +85,14 @@ return pageNode; } -- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - if (![_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]) { + if (!_pagerDataSourceImplementsNodeBlockAtIndex) { ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; return ^{ return node; }; } - ASDataControllerCellNodeBlock block = [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; - ASDisplayNodeAssertNotNil(block, @"Invalid node block. Block should be non-nil."); - return block; + return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section @@ -117,6 +117,7 @@ { if (pagerDataSource != _pagerDataSource) { _pagerDataSource = pagerDataSource; + _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; super.dataSource = (id )_proxy; } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index c63ad12a64..f7a51fc73d 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -336,11 +336,12 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). + * @returns a block that creates the node for display at this indexpath. + * Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). */ -- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; /** * Indicator to lock the data source for data fetching in async mode. diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 58dfc135ab..9cc2921edb 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -876,22 +876,24 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return node; } -- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { if (![_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]) { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + __weak __typeof__(self) weakSelf = self; return ^{ + __typeof__(self) strongSelf = weakSelf; [node enterHierarchyState:ASHierarchyStateRangeManaged]; - ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { - node.layoutDelegate = self; + node.layoutDelegate = strongSelf; } return node; }; } - ASDataControllerCellNodeBlock block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; + ASCellNodeBlock block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; __weak __typeof__(self) weakSelf = self; - ASDataControllerCellNodeBlock configuredNodeBlock = ^{ + ASCellNodeBlock configuredNodeBlock = ^{ __typeof__(self) strongSelf = weakSelf; ASCellNode *node = block(); [node enterHierarchyState:ASHierarchyStateRangeManaged]; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 01a3f9f694..1ac859cf54 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -32,7 +32,7 @@ @optional -- (ASDataControllerCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; +- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; @end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index adc1548155..90b1be92cc 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -17,7 +17,9 @@ //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) -@interface ASCollectionDataController () +@interface ASCollectionDataController () { + BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath; +} - (id)collectionDataSource; @@ -176,8 +178,8 @@ for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - ASDataControllerCellNodeBlock supplementaryCellBlock; - if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + ASCellNodeBlock supplementaryCellBlock; + if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; } else { ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; @@ -196,8 +198,8 @@ for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - ASDataControllerCellNodeBlock supplementaryCellBlock; - if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + ASCellNodeBlock supplementaryCellBlock; + if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; } else { ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; @@ -249,4 +251,10 @@ return (id)self.dataSource; } +- (void)setDataSource:(id)dataSource +{ + [super setDataSource:dataSource]; + _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; +} + @end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6630babd08..ed1f453ad8 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -24,7 +24,7 @@ typedef NSUInteger ASDataControllerAnimationOptions; /** * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. */ -typedef ASCellNode * _Nonnull(^ASDataControllerCellNodeBlock)(); +typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(); FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @@ -43,7 +43,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; /** Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. */ -- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; /** The constrained size range for layout. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index fa4006255d..ac480a91d8 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -13,11 +13,12 @@ #import "ASAssert.h" #import "ASCellNode.h" #import "ASDisplayNode.h" -#import "ASMainSerialQueue.h" -#import "ASMultidimensionalArrayUtils.h" +#import "ASFlowLayoutController.h" #import "ASInternalHelpers.h" #import "ASLayout.h" -#import "ASFlowLayoutController.h" +#import "ASMainSerialQueue.h" +#import "ASMultidimensionalArrayUtils.h" +#import "ASThread.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -44,8 +45,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _delegateDidDeleteNodes; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; - - BOOL _dataSourceImplementsAsyncNodeAtIndexPath; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -97,16 +96,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; } -- (void)setDataSource:(id)dataSource { - if (_dataSource == dataSource) { - return; - } - - _dataSource = dataSource; - // This probably won't be sufficient to tell if we should call the node block - _dataSourceImplementsAsyncNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:nodeBlockAtIndexPath:)]; -} - + (NSUInteger)parallelProcessorCount { static NSUInteger parallelProcessorCount; @@ -121,7 +110,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; @@ -176,30 +165,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); - BOOL isMainThread = [NSThread isMainThread]; for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - if (isMainThread) { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - for (NSUInteger k = j; k < j + batchCount; k++) { - ASDataControllerCellNodeBlock cellBlock = nodes[k]; - ASCellNode *node = cellBlock(); - ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); - [allocatedNodes addObject:node]; - if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; - } - } - dispatch_semaphore_signal(sema); - }); - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; - } else { + dispatch_block_t allocationBlock = ^{ for (NSUInteger k = j; k < j + batchCount; k++) { - ASDataControllerCellNodeBlock cellBlock = nodes[k]; + ASCellNodeBlock cellBlock = nodes[k]; ASCellNode *node = cellBlock(); ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); [allocatedNodes addObject:node]; @@ -207,6 +179,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; } } + }; + + if (ASDisplayNodeThreadIsMain()) { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + allocationBlock(); + dispatch_semaphore_signal(sema); + }); + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + } else { + allocationBlock(); [_mainSerialQueue performBlockOnMainThread:^{ [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; }]; @@ -423,11 +407,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; -// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { -// // Measure nodes whose views are loaded before we leave the main thread -// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; -// } - // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -454,19 +433,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// if (_dataSourceImplementsAsyncNodeAtIndexPath) { -// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; -// for (NSUInteger i = 0; i < updatedNodes.count; i++) { -// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; -// __kindof ASCellNode *cellNode = cellCreationBlock(); -// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); -// [allocatedNodes addObject:cellNode]; -// } -// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } else { -// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } - if (completion) { dispatch_async(dispatch_get_main_queue(), completion); } @@ -521,11 +487,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - if (_dataSourceImplementsAsyncNodeAtIndexPath) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; - } else { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } }]; } @@ -544,11 +506,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger j = 0; j < rowNum; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - if (_dataSourceImplementsAsyncNodeAtIndexPath) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; - } else { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } } } @@ -638,11 +596,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - -// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { -// // Measure nodes whose views are loaded before we leave the main thread -// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; -// } [self prepareForInsertSections:sections]; @@ -654,22 +607,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - -// if (_dataSourceImplementsAsyncNodeAtIndexPath) { -// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; -// for (NSUInteger i = 0; i < updatedNodes.count; i++) { -// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; -// __kindof ASCellNode *cellNode = cellCreationBlock(); -// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); -// [allocatedNodes addObject:cellNode]; -// } -// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } else { -// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } }]; }]; }]; @@ -708,14 +648,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - // Dispatch to sizing queue in order to guarantee that any in-progress sizing operations from prior edits have completed. - // For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done - // at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...] -// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { -// // Measure nodes whose views are loaded before we leave the main thread -// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; -// } - [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ @@ -729,19 +661,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // reinsert the elements [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - -// if (_dataSourceImplementsAsyncNodeAtIndexPath) { -// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; -// for (NSUInteger i = 0; i < updatedNodes.count; i++) { -// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; -// __kindof ASCellNode *cellNode = cellCreationBlock(); -// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); -// [allocatedNodes addObject:cellNode]; -// } -// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } else { -// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; -// } }]; }]; }]; @@ -837,15 +756,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - if (_dataSourceImplementsAsyncNodeAtIndexPath) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:sortedIndexPaths[i]]]; - } else { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; - } + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:sortedIndexPaths[i]]]; } - - // Measure nodes whose views are loaded before we leave the main thread -// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); @@ -891,15 +803,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - if (_dataSourceImplementsAsyncNodeAtIndexPath) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; - } else { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; - } + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } - - // Measure nodes whose views are loaded before we leave the main thread -// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index bf391db103..f9f0fb0f96 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -82,8 +82,7 @@ @implementation ASDelegateProxy { id __weak _interceptor; -@protected - NSObject * __weak _target; + id __weak _target; } - (instancetype)initWithTarget:(id )target interceptor:(id )interceptor @@ -131,12 +130,4 @@ return NO; } -- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)selector -{ - if ([_target isEqual:[NSNull null]]) { - return nil; - } - return [_target methodSignatureForSelector:selector]; -} - @end diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index 76b9cc4674..80e1282403 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -25,7 +25,8 @@ return [[ASCellNode alloc] init]; } -- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ return ^{ return [[ASCellNode alloc] init]; }; } diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 6c4be4ec2c..66fbedfe6f 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -36,7 +36,7 @@ } -- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { return ^{ ASTextCellNode *textCellNode = [ASTextCellNode new]; textCellNode.text = indexPath.description; diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index c9f581df0e..2949a16e2f 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -72,7 +72,7 @@ return nil; } -- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { return nil; } @@ -127,7 +127,7 @@ } -- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { return ^{ ASTestTextCellNode *textCellNode = [ASTestTextCellNode new]; From a9d91957b64d377ce0b28852be051ab9b0f04e28 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 4 Feb 2016 17:24:27 -0800 Subject: [PATCH 055/224] Fix ASRangeControllerBeta causing build errors --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 82eb877917..3cf4943079 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -134,7 +134,6 @@ ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; ASLayoutRangeMode rangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:selfInterfaceState - scrollDirection:_scrollDirection currentRangeMode:_currentRangeMode]; ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode From 64f36d449987360c75f3ac1ac44cb140327300be Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 4 Feb 2016 17:40:16 -0800 Subject: [PATCH 056/224] Add support for contentInset and make ASButtonNode a bit more threadsafe --- AsyncDisplayKit/ASButtonNode.h | 7 ++++ AsyncDisplayKit/ASButtonNode.mm | 74 +++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index 305121b3a6..89bd5ca245 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -37,6 +37,13 @@ */ @property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment; +/** + * @discussion insets the title and the image node + * + * @param contentEdgeInsets The insets used around the title and image node + */ +@property (nonatomic, assign) UIEdgeInsets contentEdgeInsets; + /** * Returns the styled title associated with the specified state. * diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 7ed676e744..4831ba4fb3 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -11,6 +11,7 @@ #import "ASThread.h" #import "ASDisplayNode+Subclasses.h" #import "ASBackgroundLayoutSpec.h" +#import "ASInsetLayoutSpec.h" @interface ASButtonNode () { @@ -38,6 +39,9 @@ @synthesize contentSpacing = _contentSpacing; @synthesize laysOutHorizontally = _laysOutHorizontally; +@synthesize contentVerticalAlignment = _contentVerticalAlignment; +@synthesize contentHorizontalAlignment = _contentHorizontalAlignment; +@synthesize contentEdgeInsets = _contentEdgeInsets; - (instancetype)init { @@ -53,9 +57,12 @@ [_titleNode setLayerBacked:YES]; [_imageNode setLayerBacked:YES]; [_backgroundImageNode setLayerBacked:YES]; + + [_titleNode setFlexShrink:YES]; _contentHorizontalAlignment = ASAlignmentMiddle; _contentVerticalAlignment = ASAlignmentCenter; + _contentEdgeInsets = UIEdgeInsetsZero; [self addSubnode:_backgroundImageNode]; [self addSubnode:_titleNode]; @@ -196,6 +203,42 @@ [self setNeedsLayout]; } +- (ASVerticalAlignment)contentVerticalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + return _contentVerticalAlignment; +} + +- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + _contentVerticalAlignment = contentVerticalAlignment; +} + +- (ASHorizontalAlignment)contentHorizontalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + return _contentHorizontalAlignment; +} + +- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + _contentHorizontalAlignment = contentHorizontalAlignment; +} + +- (UIEdgeInsets)contentEdgeInsets +{ + ASDN::MutexLocker l(_propertyLock); + return _contentEdgeInsets; +} + +- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets +{ + ASDN::MutexLocker l(_propertyLock); + _contentEdgeInsets = contentEdgeInsets; +} + - (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state { NSDictionary *attributes = @{ @@ -352,11 +395,18 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { + UIEdgeInsets contentEdgeInsets; + ASLayoutSpec *spec; ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; - stack.direction = self.laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - stack.spacing = self.contentSpacing; - stack.horizontalAlignment = _contentHorizontalAlignment; - stack.verticalAlignment = _contentVerticalAlignment; + { + ASDN::MutexLocker l(_propertyLock); + stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; + stack.spacing = _contentSpacing; + stack.horizontalAlignment = _contentHorizontalAlignment; + stack.verticalAlignment = _contentVerticalAlignment; + + contentEdgeInsets = _contentEdgeInsets; + } NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; if (self.imageNode.image) { @@ -369,12 +419,18 @@ stack.children = children; - if (self.backgroundImageNode.image) { - return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:stack - background:self.backgroundImageNode]; - } else { - return stack; + spec = stack; + + if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) { + spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; } + + if (self.backgroundImageNode.image) { + spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec + background:self.backgroundImageNode]; + } + + return spec; } - (void)layout From 454d68516bb3c391134f55406340afbbbe5ff5a4 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 4 Feb 2016 19:44:44 -0800 Subject: [PATCH 057/224] Remove overly-cautious thread affinity assertions for threadsafe properties. --- AsyncDisplayKit/ASDisplayNode.mm | 2 -- AsyncDisplayKit/ASDisplayNodeExtras.mm | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a5d33641c8..7bd8850579 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1475,14 +1475,12 @@ static NSInteger incrementIfFound(NSInteger i) { - (NSArray *)subnodes { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); return [_subnodes copy]; } - (ASDisplayNode *)supernode { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); return _supernode; } diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 709f3c1843..67e9185d4e 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -31,7 +31,7 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode * if (node) { block(node); } - if (!layer && [node isNodeLoaded]) { + if (!layer && [node isNodeLoaded] && ASDisplayNodeThreadIsMain()) { layer = node.layer; } From 800aa05c35f992bdea79ace011091fcfa82532e3 Mon Sep 17 00:00:00 2001 From: Connor Montgomery Date: Fri, 5 Feb 2016 13:49:00 -0500 Subject: [PATCH 058/224] Revert "Merge pull request #1162 from maicki/controlnode-init-optimization" This reverts commit 35a4b268d64e922b3928f247614b664a9413ad3b, reversing changes made to 4201df5f31077ea31bf014fc01828c065bd31c1c. --- AsyncDisplayKit/ASControlNode.m | 33 ++++++--------------------------- AsyncDisplayKit/ASTextNode.mm | 2 ++ 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 4356c66906..f68fd3a02e 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -7,7 +7,6 @@ */ #import "ASControlNode.h" -#import "ASDisplayNode+Subclasses.h" #import "ASControlNode+Subclasses.h" // UIControl allows dragging some distance outside of the control itself during @@ -78,29 +77,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v _enabled = YES; + // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. + self.userInteractionEnabled = NO; return self; } #pragma mark - ASDisplayNode Overrides -- (void)didLoad -{ - [super didLoad]; - - // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - if (![self hasTarget]) { - self.userInteractionEnabled = NO; - } -} - -- (BOOL)shouldTrackTouches -{ - return [self hasTarget] && self.enabled; -} - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.shouldTrackTouches) + if (!self.enabled) return; ASControlNodeEvent controlEventMask = 0; @@ -136,7 +122,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.shouldTrackTouches) + if (!self.enabled) return; NSParameterAssert([touches count] == 1); @@ -162,7 +148,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.shouldTrackTouches) + if (!self.enabled) return; // We're no longer tracking and there is no touch to be inside. @@ -181,7 +167,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. - if (!self.shouldTrackTouches) + if (!self.enabled) return; // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: @@ -349,16 +335,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v else removeActionFromTarget(target, action); }); - - self.userInteractionEnabled = [self hasTarget]; } #pragma mark - -- (BOOL)hasTarget -{ - return (_controlEventDispatchTable.count > 0); -} - - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event { NSParameterAssert(controlEvents != 0); diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index be732e5508..99f8f716d7 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -104,6 +104,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _shadowOpacity = [super shadowOpacity]; _shadowRadius = [super shadowRadius]; + // Disable user interaction for text node by default. + self.userInteractionEnabled = NO; self.needsDisplayOnBoundsChange = YES; _truncationMode = NSLineBreakByWordWrapping; From d6c06ab0bd0026cfce89889e16a78c7cd01e01c6 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 5 Feb 2016 14:08:49 -0800 Subject: [PATCH 059/224] Indicate that truncation mode is overridden by attributes in attributedString --- AsyncDisplayKit/ASTextNode.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 8014db0f06..11dfe74e10 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -58,6 +58,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { /** @abstract Determines how the text is truncated to fit within the receiver's maximum size. @discussion Defaults to NSLineBreakByWordWrapping. + @note Setting a truncationMode in attributedString will override the truncation mode set here. */ @property (nonatomic, assign) NSLineBreakMode truncationMode; From 6c240a2fceb867e840a9f93decbf14a1ca199b2b Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Fri, 5 Feb 2016 15:09:08 -0800 Subject: [PATCH 060/224] ASPagerNode's api was not updated while addressing comments on the initial ASCellNode background allocation PR. This change fixes that issue. --- AsyncDisplayKit/ASPagerNode.m | 2 +- AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m | 2 +- AsyncDisplayKitTests/ASCollectionViewTests.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 42408baef5..bcd275cb18 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -90,7 +90,7 @@ return pageNode; } -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); if (!_pagerDataSourceImplementsNodeBlockAtIndex) { diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index 80e1282403..c9c7910055 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -25,7 +25,7 @@ return [[ASCellNode alloc] init]; } -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { return ^{ return [[ASCellNode alloc] init]; }; } diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 66fbedfe6f..8c7f258a5a 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -36,7 +36,7 @@ } -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { return ^{ ASTextCellNode *textCellNode = [ASTextCellNode new]; textCellNode.text = indexPath.description; From 5c86eafe8fdb5a6f6b0ed7e72ea4ea9c4210b49d Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Fri, 5 Feb 2016 15:59:07 -0800 Subject: [PATCH 061/224] Add node-block api to ASPagerNodeProxy --- AsyncDisplayKit/Details/ASDelegateProxy.m | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index f9f0fb0f96..2af0e7c7d5 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -73,6 +73,7 @@ return ( // handled by ASPagerNodeDataSource node<->cell machinery selector == @selector(collectionView:nodeForItemAtIndexPath:) || + selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) || selector == @selector(collectionView:numberOfItemsInSection:) || selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) ); From 9e87813425959d9c0f6e7c7b54de787d6c6c7854 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 5 Feb 2016 21:43:14 -0800 Subject: [PATCH 062/224] Micro-optimizations in ASDisplayNode that help reduce overhead when recursing large hierarchies. --- AsyncDisplayKit/ASDisplayNode.mm | 8 ++++++++ AsyncDisplayKit/Private/ASDisplayNodeInternal.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7bd8850579..b9d1655163 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1405,6 +1405,10 @@ static NSInteger incrementIfFound(NSInteger i) { { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); + + // Profiling has shown that locking this method is benificial, so each of the property accesses don't have to lock and unlock. + ASDN::MutexLocker l(_propertyLock); + if (!self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { self.inHierarchy = YES; _flags.isEnteringHierarchy = YES; @@ -1427,6 +1431,10 @@ static NSInteger incrementIfFound(NSInteger i) { { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); + + // Profiling has shown that locking this method is benificial, so each of the property accesses don't have to lock and unlock. + ASDN::MutexLocker l(_propertyLock); + if (self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { self.inHierarchy = NO; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 6b5b4eb484..668a5fb512 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -43,7 +43,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // Allow 2^n increments of begin disabling hierarchy notifications #define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4 -#define TIME_DISPLAYNODE_OPS (DEBUG || PROFILE) +#define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE) @interface ASDisplayNode () { From 5d474bcb1a12ee14de4b191796a9de1753d900f7 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 14:17:42 -0800 Subject: [PATCH 063/224] Remove ASDisplayNode -shouldUseNewRenderingRange method and ASRangeControllerStable class --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 - AsyncDisplayKit/ASCollectionView.mm | 7 +- AsyncDisplayKit/ASDisplayNode+Beta.h | 3 - AsyncDisplayKit/ASDisplayNode.mm | 15 +- AsyncDisplayKit/ASTableView.mm | 3 +- .../ASCollectionViewLayoutController.h | 3 - .../ASCollectionViewLayoutController.mm | 80 ------ AsyncDisplayKit/Details/ASRangeController.h | 3 - AsyncDisplayKit/Details/ASRangeController.mm | 241 +----------------- .../Details/ASRangeHandlerRender.mm | 60 +---- .../Private/ASDisplayNode+UIViewBridge.mm | 12 +- AsyncDisplayKitTestHost/AppDelegate.mm | 1 - 12 files changed, 13 insertions(+), 427 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 001681acc7..9695be1a49 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -495,10 +495,6 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; - DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; }; - DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; }; - DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; }; - DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -817,8 +813,6 @@ DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; - DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerBeta.h; sourceTree = ""; }; - DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeControllerBeta.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1097,8 +1091,6 @@ 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, - DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */, - DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */, 292C599C1A956527007E5DD6 /* ASRangeHandler.h */, 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */, 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */, @@ -1399,7 +1391,6 @@ 9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */, 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */, 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */, - DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */, ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */, AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, @@ -1462,7 +1453,6 @@ B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, - DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, @@ -1853,7 +1843,6 @@ 257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, - DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, @@ -1990,7 +1979,6 @@ 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, - DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */, 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 4a3f05c25b..e60d494a47 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -167,12 +167,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; self.strongCollectionNode = collectionNode; } - _layoutController = [ASDisplayNode shouldUseNewRenderingRange] ? - [[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self] : - [[ASCollectionViewLayoutControllerStable alloc] initWithCollectionView:self]; + _layoutController = [[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self]; - _rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init] - : [[ASRangeControllerStable alloc] init]; + _rangeController = [[ASRangeControllerBeta alloc] init]; _rangeController.dataSource = self; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index d31ee49504..7cd124a7ae 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,9 +8,6 @@ @interface ASDisplayNode (Beta) -+ (BOOL)shouldUseNewRenderingRange; -+ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange; - + (BOOL)usesImplicitHierarchyManagement; + (void)setUsesImplicitHierarchyManagement:(BOOL)enabled; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b9d1655163..b479b27498 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -256,7 +256,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert([ASDisplayNode shouldUseNewRenderingRange], @"+scheduleNodeForRecursiveDisplay: should never be called without the new rendering range enabled"); static NSMutableSet *nodesToDisplay = nil; static BOOL displayScheduled = NO; static ASDN::RecursiveMutex displaySchedulerLock; @@ -1666,18 +1665,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return _flags.shouldBypassEnsureDisplay; } -static BOOL ShouldUseNewRenderingRange = YES; - -+ (BOOL)shouldUseNewRenderingRange -{ - return ShouldUseNewRenderingRange; -} - -+ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange -{ - ShouldUseNewRenderingRange = shouldUseNewRenderingRange; -} - #pragma mark - For Subclasses - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize @@ -1922,7 +1909,7 @@ static BOOL ShouldUseNewRenderingRange = YES; } else { // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. - if ([ASDisplayNode shouldUseNewRenderingRange] && !ASInterfaceStateIncludesVisible(newState)) { + if (!ASInterfaceStateIncludesVisible(newState)) { // Check __implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. if ([self __implementsDisplay]) { if (nowDisplay) { diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9cc2921edb..eeb1042fa7 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -148,8 +148,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; - _rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init] - : [[ASRangeControllerStable alloc] init]; + _rangeController = [[ASRangeControllerBeta alloc] init]; _rangeController.layoutController = _layoutController; _rangeController.dataSource = self; _rangeController.delegate = self; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h index 3f14672dcf..58745c8af4 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h @@ -19,9 +19,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASCollectionViewLayoutControllerStable : ASCollectionViewLayoutController -@end - @interface ASCollectionViewLayoutControllerBeta : ASCollectionViewLayoutController @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index bf591b2d93..4515a9f3da 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -51,86 +51,6 @@ typedef struct ASRangeGeometry ASRangeGeometry; @end -@implementation ASCollectionViewLayoutControllerStable -{ - std::vector _updateRangeBoundsIndexedByRangeType; -} - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView -{ - if (!(self = [super initWithCollectionView:collectionView])) { - return nil; - } - - _updateRangeBoundsIndexedByRangeType = std::vector(ASLayoutRangeTypeCount); - return self; -} - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection tuningParameters:tuningParameters]; - _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; - return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds]; -} - -- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds -{ - NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - if (la.representedElementCategory == UICollectionElementCategoryCell) { - [indexPathSet addObject:la.indexPath]; - } - } - return indexPathSet; -} - -- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection - tuningParameters:(ASRangeTuningParameters)tuningParameters -{ - CGRect rangeBounds = _collectionView.bounds; - CGRect updateBounds = _collectionView.bounds; - - // Scrollable directions can change for non-flow layouts - if ([_collectionViewLayout asdk_isFlowLayout] == NO) { - _scrollableDirections = [_collectionView scrollableDirections]; - } - - rangeBounds = CGRectExpandToRangeWithScrollableDirections(rangeBounds, tuningParameters, _scrollableDirections, scrollDirection); - - ASRangeTuningParameters updateTuningParameters = tuningParameters; - updateTuningParameters.leadingBufferScreenfuls = MIN(updateTuningParameters.leadingBufferScreenfuls * 0.5, 0.95); - updateTuningParameters.trailingBufferScreenfuls = MIN(updateTuningParameters.trailingBufferScreenfuls * 0.5, 0.95); - - updateBounds = CGRectExpandToRangeWithScrollableDirections(updateBounds, updateTuningParameters, _scrollableDirections, scrollDirection); - - return {rangeBounds, updateBounds}; -} - -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType -{ - CGSize viewportSize = [self viewportSize]; - CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType]; - if (CGRectIsEmpty(updateRangeBounds)) { - return YES; - } - - CGRect currentBounds = _collectionView.bounds; - if (CGRectIsEmpty(currentBounds)) { - currentBounds = CGRectMake(0, 0, viewportSize.width, viewportSize.height); - } - - if (CGRectContainsRect(updateRangeBounds, currentBounds)) { - return NO; - } else { - return YES; - } -} - -@end - - @implementation ASCollectionViewLayoutControllerBeta - (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index 6a4fde0b23..f624551107 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -77,9 +77,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASRangeControllerStable : ASRangeController -@end - @interface ASRangeControllerBeta : ASRangeController @end diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index fbcddb2351..dba59fd1df 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -38,243 +38,4 @@ return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } -@end - -@interface ASRangeControllerStable () -{ - BOOL _rangeIsValid; - - // keys should be ASLayoutRangeTypes and values NSSets containing NSIndexPaths - NSMutableDictionary *_rangeTypeIndexPaths; - NSDictionary *_rangeTypeHandlers; - BOOL _queuedRangeUpdate; - - ASScrollDirection _scrollDirection; -} - -@end - -@implementation ASRangeControllerStable - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _rangeIsValid = YES; - _rangeTypeIndexPaths = [NSMutableDictionary dictionary]; - _rangeTypeHandlers = @{ - @(ASLayoutRangeTypeDisplay) : [[ASRangeHandlerRender alloc] init], - @(ASLayoutRangeTypeFetchData): [[ASRangeHandlerPreload alloc] init], - }; - - return self; -} - -#pragma mark - Cell node view handling - -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node -{ - if (node.view.superview == contentView) { - // this content view is already correctly configured - return; - } - - // clean the content view - for (UIView *view in contentView.subviews) { - [view removeFromSuperview]; - } - - [self moveCellNode:node toView:contentView]; -} - -- (void)moveCellNode:(ASCellNode *)node toView:(UIView *)view -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); - ASDisplayNodeAssert(view, @"Cannot move a node to a non-existent view"); - - // force any nodes that are about to come into view to have display enabled - if (node.displaySuspended) { - [node recursivelySetDisplaySuspended:NO]; - } - - [view addSubview:node.view]; -} - -#pragma mark - Core visible node range managment API - -- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection -{ - _scrollDirection = scrollDirection; - - if (_queuedRangeUpdate) { - return; - } - - // coalesce these events -- handling them multiple times per runloop is noisy and expensive - _queuedRangeUpdate = YES; - - [self performSelector:@selector(_updateVisibleNodeIndexPaths) - withObject:nil - afterDelay:0 - inModes:@[ NSRunLoopCommonModes ]]; -} - -- (void)_updateVisibleNodeIndexPaths -{ - if (!_queuedRangeUpdate) { - return; - } - - NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; - - if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - _queuedRangeUpdate = NO; - return ; // don't do anything for this update, but leave _rangeIsValid to make sure we update it later - } - - NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; - CGSize viewportSize = [_dataSource viewportSizeForRangeController:self]; - [_layoutController setViewportSize:viewportSize]; - - // the layout controller needs to know what the current visible indices are to calculate range offsets - if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) { - [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; - } - - for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { - ASLayoutRangeType rangeType = (ASLayoutRangeType)i; - id rangeKey = @(rangeType); - - // this delegate decide what happens when a node is added or removed from a range - id rangeHandler = _rangeTypeHandlers[rangeKey]; - - if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths rangeType:rangeType]) { - NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - rangeMode:ASLayoutRangeModeFull - rangeType:rangeType]; - - // Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths - NSMutableSet *removedIndexPaths = _rangeIsValid ? [_rangeTypeIndexPaths[rangeKey] mutableCopy] : [NSMutableSet set]; - [removedIndexPaths minusSet:indexPaths]; - [removedIndexPaths minusSet:visibleNodePathsSet]; - - if (removedIndexPaths.count) { - NSArray *removedNodes = [_dataSource rangeController:self nodesAtIndexPaths:[removedIndexPaths allObjects]]; - for (ASCellNode *node in removedNodes) { - // since this class usually manages large or infinite data sets, the working range - // directly bounds memory usage by requiring redrawing any content that falls outside the range. - [rangeHandler node:node exitedRangeOfType:rangeType]; - } - } - - // Notify to add index paths that are not currently in _rangeTypeIndexPaths - NSMutableSet *addedIndexPaths = [indexPaths mutableCopy]; - [addedIndexPaths minusSet:_rangeTypeIndexPaths[rangeKey]]; - - // The preload range (for example) should include nodes that are visible - // TODO: remove this once we have removed the dependency on Core Animation's -display - if ([self shouldSkipVisibleNodesForRangeType:rangeType]) { - [addedIndexPaths minusSet:visibleNodePathsSet]; - } - - if (addedIndexPaths.count) { - NSArray *addedNodes = [_dataSource rangeController:self nodesAtIndexPaths:[addedIndexPaths allObjects]]; - for (ASCellNode *node in addedNodes) { - [rangeHandler node:node enteredRangeOfType:rangeType]; - } - } - - // set the range indexpaths so that we can remove/add on the next update pass - _rangeTypeIndexPaths[rangeKey] = indexPaths; - } - } - - _rangeIsValid = YES; - _queuedRangeUpdate = NO; -} - -- (BOOL)shouldSkipVisibleNodesForRangeType:(ASLayoutRangeType)rangeType -{ - return rangeType == ASLayoutRangeTypeDisplay; -} - -#pragma mark - ASDataControllerDelegete - -- (void)dataControllerBeginUpdates:(ASDataController *)dataController -{ - ASPerformBlockOnMainThread(^{ - [_delegate didBeginUpdatesInRangeController:self]; - }); -} - -- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASPerformBlockOnMainThread(^{ - [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - - // When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths, - // otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore - for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { - id rangeKey = @((ASLayoutRangeType)i); - NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy]; - for (NSIndexPath *path in indexPaths) { - [rangePaths removeObject:path]; - } - _rangeTypeIndexPaths[rangeKey] = rangePaths; - } - - [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - - // When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths, - // otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore - for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { - id rangeKey = @((ASLayoutRangeType)i); - NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy]; - for (NSIndexPath *path in _rangeTypeIndexPaths[rangeKey]) { - if ([indexSet containsIndex:path.section]) { - [rangePaths removeObject:path]; - } - } - _rangeTypeIndexPaths[rangeKey] = rangePaths; - } - - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - -@end +@end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 25859bd417..df7b4acb93 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -18,36 +18,6 @@ @end @implementation ASRangeHandlerRender -@synthesize workingWindow = _workingWindow; - -- (UIWindow *)workingWindow -{ - ASDisplayNodeAssertMainThread(); - - // we add nodes' views to this invisible window to start async rendering - // TODO: Replace this with directly triggering display https://github.com/facebook/AsyncDisplayKit/issues/315 - // Update: Latest attempt is at https://github.com/facebook/AsyncDisplayKit/pull/828 - - if (!_workingWindow && ![ASDisplayNode shouldUseNewRenderingRange]) { - _workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero]; - _workingWindow.windowLevel = UIWindowLevelNormal - 1000; - _workingWindow.userInteractionEnabled = NO; - _workingWindow.hidden = YES; - _workingWindow.alpha = 0.0; - } - - return _workingWindow; -} - -- (void)dealloc -{ - if (![ASDisplayNode shouldUseNewRenderingRange]) { - for (CALayer *layer in [self.workingWindow.layer.sublayers copy]) { - ASDisplayNode *node = layer.asyncdisplaykit_node; - [self node:node exitedRangeOfType:ASLayoutRangeTypeDisplay]; - } - } -} - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { @@ -65,16 +35,7 @@ // ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); - if ([ASDisplayNode shouldUseNewRenderingRange]) { - [node recursivelyEnsureDisplaySynchronously:NO]; - } else { - // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. - // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. - // Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will - // also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view - // being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc) - [[[self workingWindow] layer] addSublayer:node.layer]; - } + [node recursivelyEnsureDisplaySynchronously:NO]; } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType @@ -103,24 +64,9 @@ [node exitInterfaceState:ASInterfaceStateDisplay]; // ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); - - if ([ASDisplayNode shouldUseNewRenderingRange]) { - if (![node isLayerBacked]) { - [node.view removeFromSuperview]; - } else { - [node.layer removeFromSuperlayer]; - } + if (![node isLayerBacked]) { + [node.view removeFromSuperview]; } else { - if (node.layer.superlayer != [[self workingWindow] layer]) { - // In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range. - if (![node isLayerBacked]) { - // If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView). - // Layer-backed nodes will be fully handled by the unconditional removal below. - [node.view removeFromSuperview]; - } - } - - // At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell. [node.layer removeFromSuperlayer]; } } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 29e9b64af8..63afebb2ed 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -290,13 +290,11 @@ _messageToViewOrLayer(setNeedsDisplay); - if ([ASDisplayNode shouldUseNewRenderingRange]) { - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); + // FIXME: This should not need to recursively display, so create a non-recursive variant. + // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. + if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } } } diff --git a/AsyncDisplayKitTestHost/AppDelegate.mm b/AsyncDisplayKitTestHost/AppDelegate.mm index 02b188967c..79640a07a8 100644 --- a/AsyncDisplayKitTestHost/AppDelegate.mm +++ b/AsyncDisplayKitTestHost/AppDelegate.mm @@ -14,7 +14,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [ASDisplayNode setShouldUseNewRenderingRange:YES]; return YES; } From 026761c610c115860876b7815fe32fb6c0637083 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 14:39:01 -0800 Subject: [PATCH 064/224] Rename ASRangeControllerBeta to ASRangeController and use exclusively --- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- AsyncDisplayKit/Details/ASRangeController.h | 3 - AsyncDisplayKit/Details/ASRangeController.mm | 347 +++++++++++++++- .../Details/ASRangeControllerBeta.h | 20 - .../Details/ASRangeControllerBeta.mm | 374 ------------------ 6 files changed, 347 insertions(+), 401 deletions(-) delete mode 100644 AsyncDisplayKit/Details/ASRangeControllerBeta.h delete mode 100644 AsyncDisplayKit/Details/ASRangeControllerBeta.mm diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index e60d494a47..25a51674ef 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -169,7 +169,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _layoutController = [[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self]; - _rangeController = [[ASRangeControllerBeta alloc] init]; + _rangeController = [[ASRangeController alloc] init]; _rangeController.dataSource = self; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index eeb1042fa7..474d2d79d7 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -148,7 +148,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; - _rangeController = [[ASRangeControllerBeta alloc] init]; + _rangeController = [[ASRangeController alloc] init]; _rangeController.layoutController = _layoutController; _rangeController.dataSource = self; _rangeController.delegate = self; diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index f624551107..d5288f40e2 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -77,9 +77,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASRangeControllerBeta : ASRangeController -@end - /** * Data source for ASRangeController. * diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index dba59fd1df..3b6966d9c6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -10,22 +10,315 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" +#import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" #import "ASRangeHandlerVisible.h" #import "ASRangeHandlerRender.h" #import "ASRangeHandlerPreload.h" #import "ASInternalHelpers.h" -#import "ASLayoutController.h" -#import "ASLayoutRangeType.h" +#import "ASDisplayNode+FrameworkPrivate.h" + +@interface ASRangeController () +{ + BOOL _rangeIsValid; + BOOL _queuedRangeUpdate; + BOOL _layoutControllerImplementsSetVisibleIndexPaths; + ASScrollDirection _scrollDirection; + NSSet *_allPreviousIndexPaths; + ASLayoutRangeMode _currentRangeMode; + BOOL _didRegisterForNotifications; + CFAbsoluteTime _pendingDisplayNodesTimestamp; +} + +@end @implementation ASRangeController +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _rangeIsValid = YES; + _currentRangeMode = ASLayoutRangeModeInvalid; + + return self; +} + +- (void)dealloc +{ + if (_didRegisterForNotifications) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; + } +} + +#pragma mark - Core visible node range managment API + ++ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState + currentRangeMode:(ASLayoutRangeMode)currentRangeMode +{ + BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); + BOOL isFirstRangeUpdate = (currentRangeMode == ASLayoutRangeModeInvalid); + if (!isVisible || isFirstRangeUpdate) { + return ASLayoutRangeModeMinimum; + } + + return ASLayoutRangeModeFull; +} + - (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection { + _scrollDirection = scrollDirection; + [self scheduleRangeUpdate]; } +- (void)scheduleRangeUpdate +{ + if (_queuedRangeUpdate) { + return; + } + + // coalesce these events -- handling them multiple times per runloop is noisy and expensive + _queuedRangeUpdate = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self _updateVisibleNodeIndexPaths]; + }); +} + +- (void)setLayoutController:(id)layoutController +{ + _layoutController = layoutController; + _layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; +} + +- (void)_updateVisibleNodeIndexPaths +{ + ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); + if (!_queuedRangeUpdate || !_layoutController) { + return; + } + + // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges + // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; + NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; + + if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + _queuedRangeUpdate = NO; + return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later + } + + [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; + + // the layout controller needs to know what the current visible indices are to calculate range offsets + if (_layoutControllerImplementsSetVisibleIndexPaths) { + [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; + } + + // allNodes is a 2D array: it contains arrays for each section, each containing nodes. + NSArray *allNodes = [_dataSource completedNodes]; + NSUInteger numberOfSections = [allNodes count]; + + NSArray *currentSectionNodes = nil; + NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration. + NSUInteger numberOfNodesInSection = 0; + + NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths]; + NSSet *displayIndexPaths = nil; + NSSet *fetchDataIndexPaths = nil; + + // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on + // the network or display queues before preloading (offscreen) nodes are enqueued. + NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; + + ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; + ASLayoutRangeMode rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState + currentRangeMode:_currentRangeMode]; + + ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; + if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersFetchData, ASRangeTuningParametersZero)) { + fetchDataIndexPaths = visibleIndexPaths; + } else { + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:rangeMode + rangeType:ASLayoutRangeTypeFetchData]; + } + + ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode + rangeType:ASLayoutRangeTypeDisplay]; + if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { + displayIndexPaths = visibleIndexPaths; + } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { + displayIndexPaths = fetchDataIndexPaths; + } else { + displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + rangeMode:rangeMode + rangeType:ASLayoutRangeTypeDisplay]; + } + + // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. + // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. + // This means that during iteration, we will first visit visible, then display, then fetch data nodes. + [allIndexPaths unionSet:displayIndexPaths]; + [allIndexPaths unionSet:fetchDataIndexPaths]; + + // Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any + // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic + // scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all. + // Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it. + NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; + [allIndexPaths unionSet:_allPreviousIndexPaths]; + _allPreviousIndexPaths = allCurrentIndexPaths; + _currentRangeMode = rangeMode; + + if (!_rangeIsValid) { + [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; + } + + // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. + // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings + [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; + +#if RangeControllerLoggingEnabled + NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); +#endif + + for (NSIndexPath *indexPath in allIndexPaths) { + // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. + // For consistency, make sure each node knows that it should measure itself if something changes. + ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; + + if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { + if ([fetchDataIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateFetchData; + } + if ([displayIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateDisplay; + } + if ([visibleIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateVisible; + } + } else { + // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the + // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. + // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. + + // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport", + // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above. + if ([allCurrentIndexPaths containsObject:indexPath]) { + // We might be looking at an indexPath that was previously in-range, but now we need to clear it. + // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. + interfaceState |= ASInterfaceStateDisplay; + interfaceState |= ASInterfaceStateFetchData; + } + } + + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + if (section >= 0 && row >= 0 && section < numberOfSections) { + if (section != currentSectionIndex) { + // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce + // between the same ones. Still, this saves dozens of method calls to access the inner array and count. + currentSectionNodes = [allNodes objectAtIndex:section]; + numberOfNodesInSection = [currentSectionNodes count]; + currentSectionIndex = section; + } + + if (row < numberOfNodesInSection) { + ASDisplayNode *node = [currentSectionNodes objectAtIndex:row]; + + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { +#if RangeControllerLoggingEnabled + [modifiedIndexPaths addObject:indexPath]; +#endif + [node recursivelySetInterfaceState:interfaceState]; + } + } + } + } + + if (_didRegisterForNotifications) { + _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); + } + + _rangeIsValid = YES; + _queuedRangeUpdate = NO; + +#if RangeControllerLoggingEnabled + NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; + BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; + NSLog(@"visible sets are equal: %d", setsAreEqual); + if (!setsAreEqual) { + NSLog(@"standard: %@", visibleIndexPaths); + NSLog(@"custom: %@", visibleNodePathsSet); + } + + [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; + + for (NSIndexPath *indexPath in modifiedIndexPaths) { + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASInterfaceState interfaceState = node.interfaceState; + BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); + BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); + BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState); + NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData); + } +#endif +} + +#pragma mark - Notification observers + +- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState +{ + if (!_didRegisterForNotifications) { + ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState + currentRangeMode:_currentRangeMode]; + if (_currentRangeMode != nextRangeMode) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scheduledNodesDidDisplay:) + name:ASRenderingEngineDidDisplayScheduledNodesNotification + object:nil]; + _didRegisterForNotifications = YES; + } + } +} + +- (void)scheduledNodesDidDisplay:(NSNotification *)notification +{ + CFAbsoluteTime notificationTimestamp = ((NSNumber *)[notification.userInfo objectForKey:ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; + if (_pendingDisplayNodesTimestamp < notificationTimestamp) { + // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update + [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; + _didRegisterForNotifications = NO; + + [self scheduleRangeUpdate]; + } +} + +#pragma mark - Cell node view handling + - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node { + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); + ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); + + if (node.view.superview == contentView) { + // this content view is already correctly configured + return; + } + + // clean the content view + for (UIView *view in contentView.subviews) { + [view removeFromSuperview]; + } + + [contentView addSubview:node.view]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType @@ -38,4 +331,54 @@ return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } +#pragma mark - ASDataControllerDelegete + +- (void)dataControllerBeginUpdates:(ASDataController *)dataController +{ + ASPerformBlockOnMainThread(^{ + [_delegate didBeginUpdatesInRangeController:self]; + }); +} + +- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +{ + ASPerformBlockOnMainThread(^{ + [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + @end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.h b/AsyncDisplayKit/Details/ASRangeControllerBeta.h deleted file mode 100644 index 7c8385f8c1..0000000000 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.h +++ /dev/null @@ -1,20 +0,0 @@ -/* 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 - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -// ASRangeControllerBeta defined in ASRangeController.h - -NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm deleted file mode 100644 index 3cf4943079..0000000000 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ /dev/null @@ -1,374 +0,0 @@ -/* 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 "ASRangeControllerBeta.h" - -#import "ASAssert.h" -#import "ASDisplayNodeExtras.h" -#import "ASDisplayNodeInternal.h" -#import "ASMultiDimensionalArrayUtils.h" -#import "ASRangeHandlerVisible.h" -#import "ASRangeHandlerRender.h" -#import "ASRangeHandlerPreload.h" -#import "ASInternalHelpers.h" -#import "ASDisplayNode+FrameworkPrivate.h" - -@interface ASRangeControllerBeta () -{ - BOOL _rangeIsValid; - BOOL _queuedRangeUpdate; - BOOL _layoutControllerImplementsSetVisibleIndexPaths; - ASScrollDirection _scrollDirection; - NSSet *_allPreviousIndexPaths; - ASLayoutRangeMode _currentRangeMode; - BOOL _didRegisterForNotifications; - CFAbsoluteTime _pendingDisplayNodesTimestamp; -} - -@end - -@implementation ASRangeControllerBeta - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _rangeIsValid = YES; - _currentRangeMode = ASLayoutRangeModeInvalid; - - return self; -} - -- (void)dealloc -{ - if (_didRegisterForNotifications) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - } -} - -#pragma mark - Core visible node range managment API - -+ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState - currentRangeMode:(ASLayoutRangeMode)currentRangeMode -{ - BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); - BOOL isFirstRangeUpdate = (currentRangeMode == ASLayoutRangeModeInvalid); - if (!isVisible || isFirstRangeUpdate) { - return ASLayoutRangeModeMinimum; - } - - return ASLayoutRangeModeFull; -} - -- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection -{ - _scrollDirection = scrollDirection; - [self scheduleRangeUpdate]; -} - -- (void)scheduleRangeUpdate -{ - if (_queuedRangeUpdate) { - return; - } - - // coalesce these events -- handling them multiple times per runloop is noisy and expensive - _queuedRangeUpdate = YES; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self _updateVisibleNodeIndexPaths]; - }); -} - -- (void)setLayoutController:(id)layoutController -{ - _layoutController = layoutController; - _layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; -} - -- (void)_updateVisibleNodeIndexPaths -{ - ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); - if (!_queuedRangeUpdate || !_layoutController) { - return; - } - - // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges - // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; - NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; - - if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - _queuedRangeUpdate = NO; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } - - [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; - - // the layout controller needs to know what the current visible indices are to calculate range offsets - if (_layoutControllerImplementsSetVisibleIndexPaths) { - [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; - } - - // allNodes is a 2D array: it contains arrays for each section, each containing nodes. - NSArray *allNodes = [_dataSource completedNodes]; - NSUInteger numberOfSections = [allNodes count]; - - NSArray *currentSectionNodes = nil; - NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration. - NSUInteger numberOfNodesInSection = 0; - - NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths]; - NSSet *displayIndexPaths = nil; - NSSet *fetchDataIndexPaths = nil; - - // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on - // the network or display queues before preloading (offscreen) nodes are enqueued. - NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; - - ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - ASLayoutRangeMode rangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:selfInterfaceState - currentRangeMode:_currentRangeMode]; - - ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypeFetchData]; - if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersFetchData, ASRangeTuningParametersZero)) { - fetchDataIndexPaths = visibleIndexPaths; - } else { - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - rangeMode:rangeMode - rangeType:ASLayoutRangeTypeFetchData]; - } - - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypeDisplay]; - if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { - displayIndexPaths = visibleIndexPaths; - } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { - displayIndexPaths = fetchDataIndexPaths; - } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - rangeMode:rangeMode - rangeType:ASLayoutRangeTypeDisplay]; - } - - // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. - // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. - // This means that during iteration, we will first visit visible, then display, then fetch data nodes. - [allIndexPaths unionSet:displayIndexPaths]; - [allIndexPaths unionSet:fetchDataIndexPaths]; - - // Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any - // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic - // scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all. - // Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it. - NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; - [allIndexPaths unionSet:_allPreviousIndexPaths]; - _allPreviousIndexPaths = allCurrentIndexPaths; - _currentRangeMode = rangeMode; - - if (!_rangeIsValid) { - [allIndexPaths addObjectsFromArray:ASIndexPathsForMultidimensionalArray(allNodes)]; - } - - // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. - // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings - [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; - -#if RangeControllerLoggingEnabled - NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); -#endif - - for (NSIndexPath *indexPath in allIndexPaths) { - // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. - // For consistency, make sure each node knows that it should measure itself if something changes. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; - - if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { - if ([fetchDataIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateFetchData; - } - if ([displayIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateDisplay; - } - if ([visibleIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateVisible; - } - } else { - // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the - // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. - // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - - // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport", - // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above. - if ([allCurrentIndexPaths containsObject:indexPath]) { - // We might be looking at an indexPath that was previously in-range, but now we need to clear it. - // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. - interfaceState |= ASInterfaceStateDisplay; - interfaceState |= ASInterfaceStateFetchData; - } - } - - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - - if (section >= 0 && row >= 0 && section < numberOfSections) { - if (section != currentSectionIndex) { - // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce - // between the same ones. Still, this saves dozens of method calls to access the inner array and count. - currentSectionNodes = [allNodes objectAtIndex:section]; - numberOfNodesInSection = [currentSectionNodes count]; - currentSectionIndex = section; - } - - if (row < numberOfNodesInSection) { - ASDisplayNode *node = [currentSectionNodes objectAtIndex:row]; - - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { -#if RangeControllerLoggingEnabled - [modifiedIndexPaths addObject:indexPath]; -#endif - [node recursivelySetInterfaceState:interfaceState]; - } - } - } - } - - if (_didRegisterForNotifications) { - _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); - } - - _rangeIsValid = YES; - _queuedRangeUpdate = NO; - -#if RangeControllerLoggingEnabled - NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; - BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; - NSLog(@"visible sets are equal: %d", setsAreEqual); - if (!setsAreEqual) { - NSLog(@"standard: %@", visibleIndexPaths); - NSLog(@"custom: %@", visibleNodePathsSet); - } - - [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - - for (NSIndexPath *indexPath in modifiedIndexPaths) { - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; - ASInterfaceState interfaceState = node.interfaceState; - BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); - BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); - BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState); - NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData); - } -#endif -} - -#pragma mark - Notification observers - -- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState -{ - if (!_didRegisterForNotifications) { - ASLayoutRangeMode nextRangeMode = [ASRangeControllerBeta rangeModeForInterfaceState:interfaceState - currentRangeMode:_currentRangeMode]; - if (_currentRangeMode != nextRangeMode) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scheduledNodesDidDisplay:) - name:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil]; - _didRegisterForNotifications = YES; - } - } -} - -- (void)scheduledNodesDidDisplay:(NSNotification *)notification -{ - CFAbsoluteTime notificationTimestamp = ((NSNumber *)[notification.userInfo objectForKey:ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; - if (_pendingDisplayNodesTimestamp < notificationTimestamp) { - // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - _didRegisterForNotifications = NO; - - [self scheduleRangeUpdate]; - } -} - -#pragma mark - Cell node view handling - -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); - ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); - - if (node.view.superview == contentView) { - // this content view is already correctly configured - return; - } - - // clean the content view - for (UIView *view in contentView.subviews) { - [view removeFromSuperview]; - } - - [contentView addSubview:node.view]; -} - -#pragma mark - ASDataControllerDelegete - -- (void)dataControllerBeginUpdates:(ASDataController *)dataController -{ - ASPerformBlockOnMainThread(^{ - [_delegate didBeginUpdatesInRangeController:self]; - }); -} - -- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASPerformBlockOnMainThread(^{ - [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - -@end From 03536ddefd24164852f5a6084acc653e34256248 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 14:45:43 -0800 Subject: [PATCH 065/224] Remove shouldUpdateForVisibleIndexPaths This method can be removed as ASRangeControllerBeta is the main ASRangeController now --- .../Details/ASAbstractLayoutController.mm | 7 ----- .../Details/ASFlowLayoutController.mm | 28 ------------------- AsyncDisplayKit/Details/ASLayoutController.h | 4 --- 3 files changed, 39 deletions(-) diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 2edcd4a78f..38828861e7 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -82,13 +82,6 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar #pragma mark - Abstract Index Path Range Support -// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssertNotSupported(); - return NO; -} - - (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertNotSupported(); diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index cbcde0f011..dce8065e8e 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -15,8 +15,6 @@ #include #include -static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; - @interface ASFlowLayoutController() { ASIndexPathRange _visibleRange; @@ -39,32 +37,6 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; #pragma mark - Visible Indices -// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType -{ - if (!indexPaths.count || rangeType >= _rangesByType.size()) { - return NO; - } - - ASIndexPathRange existingRange = _rangesByType[rangeType]; - ASIndexPathRange newRange = [self indexPathRangeForIndexPaths:indexPaths]; - - ASIndexPath maximumStart = ASIndexPathMaximum(existingRange.start, newRange.start); - ASIndexPath minimumEnd = ASIndexPathMinimum(existingRange.end, newRange.end); - - if (ASIndexPathEqualToIndexPath(maximumStart, existingRange.start) || ASIndexPathEqualToIndexPath(minimumEnd, existingRange.end)) { - return YES; - } - - NSInteger newStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, newRange.start)]; - NSInteger existingStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, existingRange.start)] * kASFlowLayoutControllerRefreshingThreshold; - - NSInteger newEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, newRange.end)]; - NSInteger existingEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, existingRange.end)] * kASFlowLayoutControllerRefreshingThreshold; - - return (newStartDelta > existingStartDelta) || (newEndDelta > existingEndDelta); -} - - (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths { _visibleRange = [self indexPathRangeForIndexPaths:indexPaths]; diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 0ed8658876..8cf34effd0 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -31,10 +31,6 @@ FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRan - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; -// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. -// TODO: Now that it is the main version, can we remove this now? -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType; - - (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; @optional From e49a5d992c6cf30d127c677b7c14f75e67032c2c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 15:27:15 -0800 Subject: [PATCH 066/224] Remove unused ASCollectionViewLayoutController import --- AsyncDisplayKit/ASTableView.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 474d2d79d7..28d8ef3de9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -11,7 +11,6 @@ #import "ASAssert.h" #import "ASBatchFetching.h" #import "ASChangeSetDataController.h" -#import "ASCollectionViewLayoutController.h" #import "ASDelegateProxy.h" #import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" From fa2a058585ca7e860eca8f0b4842d3711b9f8bd9 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 15:27:45 -0800 Subject: [PATCH 067/224] Rename ASCollectionViewLayoutControllerBeta to ASCollectionViewLayoutController --- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/Details/ASCollectionViewLayoutController.h | 3 --- AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm | 4 ---- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 25a51674ef..23ae37aec8 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -167,7 +167,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; self.strongCollectionNode = collectionNode; } - _layoutController = [[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self]; + _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; _rangeController = [[ASRangeController alloc] init]; _rangeController.dataSource = self; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h index 58745c8af4..632ba46bf8 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h @@ -19,7 +19,4 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASCollectionViewLayoutControllerBeta : ASCollectionViewLayoutController -@end - NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index 4515a9f3da..0d5f69bdf5 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -49,10 +49,6 @@ typedef struct ASRangeGeometry ASRangeGeometry; return self; } -@end - -@implementation ASCollectionViewLayoutControllerBeta - - (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; From ef95394bac9efc54b7d6755eea6c9e5ecebd7268 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 17:18:24 -0800 Subject: [PATCH 068/224] Add locking to ASControlNode --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++++++------ .../{ASControlNode.m => ASControlNode.mm} | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) rename AsyncDisplayKit/{ASControlNode.m => ASControlNode.mm} (98%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 001681acc7..886534518f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -49,7 +49,7 @@ 058D09C1195D04C000B7D73C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09C0195D04C000B7D73C /* UIKit.framework */; }; 058D09C4195D04C000B7D73C /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; 058D09CA195D04C000B7D73C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 058D09C8195D04C000B7D73C /* InfoPlist.strings */; }; - 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.m */; }; + 058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.mm */; }; 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D9195D050800B7D73C /* ASDisplayNode.mm */; }; 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */; }; 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DE195D050800B7D73C /* ASImageNode.mm */; }; @@ -379,7 +379,7 @@ B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.m */; }; + B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D6195D050800B7D73C /* ASControlNode.mm */; }; B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D8195D050800B7D73C /* ASDisplayNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09D9195D050800B7D73C /* ASDisplayNode.mm */; }; @@ -579,7 +579,7 @@ 058D09C7195D04C000B7D73C /* AsyncDisplayKitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AsyncDisplayKitTests-Info.plist"; sourceTree = ""; }; 058D09C9195D04C000B7D73C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 058D09D5195D050800B7D73C /* ASControlNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASControlNode.h; sourceTree = ""; }; - 058D09D6195D050800B7D73C /* ASControlNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNode.m; sourceTree = ""; }; + 058D09D6195D050800B7D73C /* ASControlNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASControlNode.mm; sourceTree = ""; }; 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Subclasses.h"; sourceTree = ""; }; 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; }; 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNode.mm; sourceTree = ""; }; @@ -952,7 +952,7 @@ DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */, DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */, 058D09D5195D050800B7D73C /* ASControlNode.h */, - 058D09D6195D050800B7D73C /* ASControlNode.m */, + 058D09D6195D050800B7D73C /* ASControlNode.mm */, DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, @@ -1798,7 +1798,7 @@ DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, - 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */, + 058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */, 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */, B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */, 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */, @@ -1940,7 +1940,7 @@ 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, - B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */, + B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.mm similarity index 98% rename from AsyncDisplayKit/ASControlNode.m rename to AsyncDisplayKit/ASControlNode.mm index f68fd3a02e..d4a0598f39 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.mm @@ -8,6 +8,7 @@ #import "ASControlNode.h" #import "ASControlNode+Subclasses.h" +#import "ASThread.h" // UIControl allows dragging some distance outside of the control itself during // tracking. This value depends on the device idiom (25 or 70 points), so @@ -21,6 +22,8 @@ @interface ASControlNode () { @private + ASDN::RecursiveMutex _controlLock; + // Control Attributes BOOL _enabled; BOOL _highlighted; @@ -217,10 +220,12 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(action); NSParameterAssert(controlEventMask != 0); + ASDN::MutexLocker l(_controlLock); + // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. if (!target) target = [NSNull null]; - + if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. } @@ -262,6 +267,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(target); NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); + ASDN::MutexLocker l(_controlLock); + // Grab the event dispatch table for this event. NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; if (!eventDispatchTable) @@ -273,6 +280,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (NSSet *)allTargets { + ASDN::MutexLocker l(_controlLock); + NSMutableSet *targets = [[NSMutableSet alloc] init]; // Look at each event... @@ -289,6 +298,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask { NSParameterAssert(controlEventMask != 0); + + ASDN::MutexLocker l(_controlLock); // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ @@ -341,6 +352,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event { NSParameterAssert(controlEvents != 0); + + ASDN::MutexLocker l(_controlLock); // Enumerate the events in the mask, invoking the target-action pairs for each. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ From 545b3e7e5fa88bc3b5efbd98f65a1dbfe7bf7cb3 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 19:12:02 -0800 Subject: [PATCH 069/224] Rename lock instance variable to be consistent with ASControlNode and ASImageNode lock variable naming --- AsyncDisplayKit/ASVideoNode.mm | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 6ba344d35a..e2efb5a80e 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -11,7 +11,7 @@ @interface ASVideoNode () { - ASDN::RecursiveMutex _lock; + ASDN::RecursiveMutex _videoLock; __weak id _delegate; @@ -209,7 +209,7 @@ } { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); _currentItem = [[AVPlayerItem alloc] initWithAsset:_asset]; [_currentItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; @@ -227,7 +227,7 @@ [super clearFetchedData]; { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); ((AVPlayerLayer *)_playerNode.layer).player = nil; _player = nil; } @@ -235,7 +235,7 @@ - (void)visibilityDidChange:(BOOL)isVisible { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); if (_shouldAutoplay && _playerNode.isNodeLoaded) { [self play]; @@ -261,7 +261,7 @@ - (void)setPlayButton:(ASButtonNode *)playButton { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); _playButton = playButton; @@ -272,14 +272,14 @@ - (ASButtonNode *)playButton { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _playButton; } - (void)setAsset:(AVAsset *)asset { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); if (ASObjectIsEqual(((AVURLAsset *)asset).URL, ((AVURLAsset *)_asset).URL)) { return; @@ -295,19 +295,19 @@ - (AVAsset *)asset { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _asset; } - (AVPlayer *)player { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _player; } - (void)setGravity:(NSString *)gravity { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); if (_playerNode.isNodeLoaded) { ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; } @@ -316,21 +316,21 @@ - (NSString *)gravity { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _gravity; } - (BOOL)muted { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _muted; } - (void)setMuted:(BOOL)muted { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); _muted = muted; } @@ -339,7 +339,7 @@ - (void)play { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); if (!_spinner) { _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ @@ -389,7 +389,7 @@ - (void)pause { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); [_player pause]; [((UIActivityIndicatorView *)_spinner.view) stopAnimating]; @@ -401,7 +401,7 @@ - (BOOL)isPlaying { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return (_player.rate > 0 && !_player.error); } @@ -410,31 +410,31 @@ - (ASDisplayNode *)spinner { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _spinner; } - (AVPlayerItem *)curentItem { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _currentItem; } - (void)setCurrentItem:(AVPlayerItem *)currentItem { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); _currentItem = currentItem; } - (ASDisplayNode *)playerNode { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _playerNode; } - (BOOL)shouldBePlaying { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(_videoLock); return _shouldBePlaying; } From e9d50ff3a14f618a3d372b9b4cd6c1dca8d1f4e6 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 7 Feb 2016 20:24:40 -0800 Subject: [PATCH 070/224] Disable App Transport Security in AsyncDisplayKitTestsHost target To be able to make http requests in tests we should disable the NSAppTransportSecurity of the AsyncDisplayKitTestsHost target. Otherwise tests like -[ASBasicImageDownloaderTests testAsynchronouslyDownloadTheSameURLTwice] will show an error. --- AsyncDisplayKitTestHost/Info.plist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AsyncDisplayKitTestHost/Info.plist b/AsyncDisplayKitTestHost/Info.plist index 20d72f5238..074288945b 100644 --- a/AsyncDisplayKitTestHost/Info.plist +++ b/AsyncDisplayKitTestHost/Info.plist @@ -22,6 +22,11 @@ 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UIRequiredDeviceCapabilities armv7 From 8dc576fed704e51758e3d9dd9cbfdf18c35a1577 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 8 Feb 2016 10:13:18 -0800 Subject: [PATCH 071/224] Remove .ASVideoNode.mm.un~ --- AsyncDisplayKit/.ASVideoNode.mm.un~ | Bin 1821 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 AsyncDisplayKit/.ASVideoNode.mm.un~ diff --git a/AsyncDisplayKit/.ASVideoNode.mm.un~ b/AsyncDisplayKit/.ASVideoNode.mm.un~ deleted file mode 100644 index c3a408059e398a4b434ff0cea05935aefb258a98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1821 zcmWH`%$*;a=aT=Ff$3qysY^T0UjB0OiG@Osvcr|#p3dJUuC7v!YO{YPbaB&O1_s7y z45)w+$Yp^FfoK?J2wS?|m<1%u$Pj-4s(=YfgD8-c9*qAV2*4sRfl->uApndHeQlv0>vCi3mA;X7Az!aMFV5Y5IwexULijXrC$Q_{QGKD#4 oG>Tv$K`R; Date: Mon, 8 Feb 2016 12:57:05 -0800 Subject: [PATCH 072/224] Remove ASRangeHandler protocol with all classes that implement it With the move to the new ASRangeController we don't need the ASRangeHandler protocol anymore --- AsyncDisplayKit.xcodeproj/project.pbxproj | 42 ----------- AsyncDisplayKit/AsyncDisplayKit.h | 3 - AsyncDisplayKit/Details/ASRangeController.mm | 3 - AsyncDisplayKit/Details/ASRangeHandler.h | 26 ------- .../Details/ASRangeHandlerPreload.h | 19 ----- .../Details/ASRangeHandlerPreload.mm | 27 ------- .../Details/ASRangeHandlerRender.h | 19 ----- .../Details/ASRangeHandlerRender.mm | 74 ------------------- .../Details/ASRangeHandlerVisible.h | 15 ---- .../Details/ASRangeHandlerVisible.mm | 25 ------- 10 files changed, 253 deletions(-) delete mode 100644 AsyncDisplayKit/Details/ASRangeHandler.h delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerPreload.h delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerPreload.mm delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerRender.h delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerRender.mm delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerVisible.h delete mode 100644 AsyncDisplayKit/Details/ASRangeHandlerVisible.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 9695be1a49..859858ca93 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -186,10 +186,6 @@ 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; - 258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; }; - 258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; }; - 25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; }; - 25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; }; 25E327561C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25E327581C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; @@ -199,11 +195,6 @@ 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 292C59A01A956527007E5DD6 /* ASRangeHandlerPreload.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 292C59A11A956527007E5DD6 /* ASRangeHandlerPreload.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */; }; - 292C59A21A956527007E5DD6 /* ASRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599C1A956527007E5DD6 /* ASRangeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 292C59A31A956527007E5DD6 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; }; 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; }; 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */; }; 299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -424,11 +415,6 @@ B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; }; B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; - B35062281B010EFD0018CF92 /* ASRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599C1A956527007E5DD6 /* ASRangeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062291B010EFD0018CF92 /* ASRangeHandlerPreload.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */; }; - B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; }; B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062391B010EFD0018CF92 /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -684,17 +670,10 @@ 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitCoreTextAdditions.h; path = TextKit/ASTextKitCoreTextAdditions.h; sourceTree = ""; }; 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeTypes.h; path = TextKit/ASTextNodeTypes.h; sourceTree = ""; }; 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = ""; }; - 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = ""; }; - 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = ""; }; 25E327541C16819500A2170C /* ASPagerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASPagerNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 25E327551C16819500A2170C /* ASPagerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASPagerNode.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; - 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = ""; }; - 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerPreload.mm; sourceTree = ""; }; - 292C599C1A956527007E5DD6 /* ASRangeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandler.h; sourceTree = ""; }; - 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerRender.h; sourceTree = ""; }; - 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerRender.mm; sourceTree = ""; }; 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloaderInternal.h; sourceTree = ""; }; 296A0A311A951715005ACEAA /* ASScrollDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASScrollDirection.h; path = AsyncDisplayKit/Details/ASScrollDirection.h; sourceTree = SOURCE_ROOT; }; 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchFetchingTests.m; sourceTree = ""; }; @@ -1091,13 +1070,6 @@ 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, - 292C599C1A956527007E5DD6 /* ASRangeHandler.h */, - 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */, - 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */, - 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */, - 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */, - 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */, - 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */, 296A0A311A951715005ACEAA /* ASScrollDirection.h */, 205F0E111B371BD7007741D0 /* ASScrollDirection.m */, 058D0A12195D050800B7D73C /* ASThread.h */, @@ -1360,7 +1332,6 @@ DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, - 258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, @@ -1403,9 +1374,6 @@ 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */, ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */, - 292C59A21A956527007E5DD6 /* ASRangeHandler.h in Headers */, - 292C59A01A956527007E5DD6 /* ASRangeHandlerPreload.h in Headers */, - 292C59A31A956527007E5DD6 /* ASRangeHandlerRender.h in Headers */, ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */, AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */, 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */, @@ -1447,7 +1415,6 @@ buildActionMask = 2147483647; files = ( AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, - 25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */, B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, @@ -1531,9 +1498,6 @@ B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, - B35062281B010EFD0018CF92 /* ASRangeHandler.h in Headers */, - B35062291B010EFD0018CF92 /* ASRangeHandlerPreload.h in Headers */, - B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, @@ -1828,8 +1792,6 @@ 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */, - 292C59A11A956527007E5DD6 /* ASRangeHandlerPreload.mm in Sources */, - 292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */, 257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */, ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */, @@ -1837,7 +1799,6 @@ D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, - 258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, 257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */, @@ -1966,14 +1927,11 @@ 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */, - B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */, - B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */, 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 34EFC7661B701CD200AD841F /* ASRelativeSize.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, - 25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */, B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */, 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index b854d7564a..2ad55f9080 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -63,9 +63,6 @@ #import #import #import -#import -#import -#import #import #import #import diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 3b6966d9c6..1b8d7d8f89 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -12,9 +12,6 @@ #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" -#import "ASRangeHandlerVisible.h" -#import "ASRangeHandlerRender.h" -#import "ASRangeHandlerPreload.h" #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" diff --git a/AsyncDisplayKit/Details/ASRangeHandler.h b/AsyncDisplayKit/Details/ASRangeHandler.h deleted file mode 100644 index 136736f442..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandler.h +++ /dev/null @@ -1,26 +0,0 @@ -/* 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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASDisplayNode; - -@protocol ASRangeHandler - -@required - -- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType; -- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType; - -@end - -NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.h b/AsyncDisplayKit/Details/ASRangeHandlerPreload.h deleted file mode 100644 index eb7c34f991..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.h +++ /dev/null @@ -1,19 +0,0 @@ -/* 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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASRangeHandlerPreload : NSObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm deleted file mode 100644 index 3eccec5dd8..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ /dev/null @@ -1,27 +0,0 @@ -/* 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 "ASRangeHandlerPreload.h" -#import "ASDisplayNode.h" -#import "ASDisplayNode+FrameworkPrivate.h" - -@implementation ASRangeHandlerPreload - -- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges"); - [node enterInterfaceState:ASInterfaceStateFetchData]; -} - -- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges"); - [node exitInterfaceState:ASInterfaceStateFetchData]; -} - -@end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.h b/AsyncDisplayKit/Details/ASRangeHandlerRender.h deleted file mode 100644 index 8b00982b99..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.h +++ /dev/null @@ -1,19 +0,0 @@ -/* 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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASRangeHandlerRender : NSObject - -@end - -NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm deleted file mode 100644 index df7b4acb93..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ /dev/null @@ -1,74 +0,0 @@ -/* 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 "ASRangeHandlerRender.h" - -#import "ASDisplayNode.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" - -@interface ASRangeHandlerRender () -@property (nonatomic,readonly) UIWindow *workingWindow; -@end - -@implementation ASRangeHandlerRender - -- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges"); - - // If a node had previously been onscreen but now is only in the working range, - // ensure its view is not orphaned in a UITableViewCell in the reuse pool. - if (![node isLayerBacked] && node.view.superview) { - [node.view removeFromSuperview]; - } - - // The node un-suspends display. - [node enterInterfaceState:ASInterfaceStateDisplay]; - - -// ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); - [node recursivelyEnsureDisplaySynchronously:NO]; -} - -- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges"); - - // This code is tricky. There are several possible states a node can be in when it reaches this point. - // 1. Layer-backed vs view-backed nodes. AS of this writing, only ASCellNodes arrive here, which are always view-backed — - // but we maintain correctness for all ASDisplayNodes, including layer-backed ones. - // (Note: it would not make sense to pass in a subnode of a rasterized node here, so that is unsupported). - // 2. The node's layer may have been added to the workingWindow previously, or it may have never been added, such as if rangeTuningParameter's leading value is 0. - // 3. The node's layer may not be present in the workingWindow, even if it was previously added. - // This is a common case, as once the node is added to an active cell contentsView (e.g. visible), it is automatically removed from the workingWindow. - // The system does this when addSublayer is called, even if removeFromSuperlayer is never explicitly called. - // 4. Lastly and most unusually, it is possible for a node to be offscreen, completely outside the heirarchy, and yet considered within the working range. - // This happens if the UITableViewCell is reused after scrolling offscreen. Because the node has already been given the opportunity to display, we do not - // proactively re-host it within the workingWindow (improving efficiency). Some time later, it may fall outside the working range, in which case calling - // -recursivelyClearContents is critical. If the user scrolls back and it is re-hosted in a UITableViewCell, the content will still exist as it is not cleared - // by simply being removed from the cell. The code that usually triggers this condition is the -removeFromSuperview in -[ASRangeController configureContentView:forCellNode:]. - // Condition #4 is suboptimal in some cases, as it is conceivable that memory warnings could trigger clearing content that is inside the working range. However, enforcing the - // preservation of this content could result in the app being killed, which is not likely preferable over briefly seeing placeholders in the event the user scrolls backwards. - // Nonetheless, future changes to the implementation will likely eliminate this behavior to simplify debugging and extensibility of working range functionality. - - // The node calls clearCurrentContents and suspends display - [node exitInterfaceState:ASInterfaceStateDisplay]; - -// ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); - if (![node isLayerBacked]) { - [node.view removeFromSuperview]; - } else { - [node.layer removeFromSuperlayer]; - } -} - -@end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.h b/AsyncDisplayKit/Details/ASRangeHandlerVisible.h deleted file mode 100644 index eab9f77f29..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerVisible.h +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 - -#import - -@interface ASRangeHandlerVisible : NSObject - -@end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm deleted file mode 100644 index f17bc1cad3..0000000000 --- a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm +++ /dev/null @@ -1,25 +0,0 @@ -/* 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 "ASRangeHandlerVisible.h" -#import "ASDisplayNode.h" -#import "ASDisplayNode+FrameworkPrivate.h" - -@implementation ASRangeHandlerVisible - -- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType -{ - [node enterInterfaceState:ASInterfaceStateVisible]; -} - -- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType -{ - [node exitInterfaceState:ASInterfaceStateVisible]; -} - -@end From 5a4e4dcac436baad23cd351afefe12a4ff6cfe45 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 8 Feb 2016 16:00:19 -0800 Subject: [PATCH 073/224] Add default PINRemoteImageDownloader --- AsyncDisplayKit.podspec | 12 ++ AsyncDisplayKit/ASMultiplexImageNode.mm | 149 +++++++++++++++--- AsyncDisplayKit/ASNetworkImageNode.h | 6 +- AsyncDisplayKit/ASNetworkImageNode.mm | 137 +++++++++++++--- .../Details/ASBasicImageDownloader.mm | 2 +- AsyncDisplayKit/Details/ASImageProtocols.h | 107 +++++++++---- .../Details/ASPINRemoteImageDownloader.h | 16 ++ .../Details/ASPINRemoteImageDownloader.m | 97 ++++++++++++ .../ASMultiplexImageNodeTests.m | 18 +-- examples/Multiplex/Sample/ScreenNode.m | 2 +- 10 files changed, 457 insertions(+), 89 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h create mode 100644 AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index f93552b3c0..9b47f55c84 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -37,6 +37,9 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/Details/ASDealloc2MainObject.h', 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', ] + + #Subspecs + spec.subspec 'ASDealloc2MainObject' do |mrr| mrr.requires_arc = false mrr.source_files = [ @@ -45,6 +48,15 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/Details/ASDealloc2MainObject.m', ] end + + spec.subspec 'PINRemoteImage' do |pin| + pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } + pin.dependency 'PINRemoteImage' + pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + end + + # Include optional FLAnimatedImage module + spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.library = 'c++' diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index ff35ed3aec..526d1be1ef 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -26,6 +26,12 @@ #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. #endif +#if PIN_REMOTE_IMAGE +#import "ASPINRemoteImageDownloader.h" +#else +#import "ASBasicImageDownloader.h" +#endif + NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; static NSString *const kAssetsLibraryURLScheme = @"assets-library"; @@ -72,7 +78,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent __weak NSOperation *_phImageRequestOperation; // Networking. + ASDN::Mutex _downloadIdentifierLock; id _downloadIdentifier; + + //set on init only + BOOL _downloaderSupportsNewProtocol; + BOOL _downloaderImplementsSetProgress; + BOOL _downloaderImplementsSetPriority; + BOOL _cacherSupportsNewProtocol; } //! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. @@ -162,6 +175,18 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; + + NSAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); + + _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] ? YES : NO; + + NSAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); + + _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)] ? YES : NO; + _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO; + + _cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO; + self.shouldBypassEnsureDisplay = YES; return self; @@ -169,8 +194,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (instancetype)init { - ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); - return [self initWithCache:nil downloader:nil]; // satisfy compiler +#if PIN_REMOTE_IMAGE + return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; +#else + return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; +#endif } - (void)dealloc @@ -238,6 +266,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } } +- (void)displayWillStart +{ + [super displayWillStart]; + + [self fetchData]; + + if (_downloaderImplementsSetPriority) { + { + ASDN::MutexLocker l(_downloadIdentifierLock); + if (_downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier]; + } + } + } + + if (self.image == nil) { + if (_downloaderImplementsSetProgress) { + { + ASDN::MutexLocker l(_downloadIdentifierLock); + + if (_downloadIdentifier != nil) { + __weak __typeof__(self) weakSelf = self; + [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + return; + } + + strongSelf.image = progressImage; + } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; + } + } + } + } +} + #pragma mark - Core - (void)setDelegate:(id )delegate @@ -331,10 +401,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)_setDownloadIdentifier:(id)downloadIdentifier { + ASDN::MutexLocker l(_downloadIdentifierLock); if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier)) return; - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + if (_downloadIdentifier) { + [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + } _downloadIdentifier = downloadIdentifier; } @@ -622,10 +695,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); if (_cache) { - [_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) { - UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); - completionBlock(imageFromCache); - }]; + if (_cacherSupportsNewProtocol) { + [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(UIImage *imageFromCache) { + completionBlock(imageFromCache); + }]; + } else { + [_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) { + UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); + completionBlock(imageFromCache); + }]; + } } // If we don't have a cache, just fail immediately. else { @@ -655,22 +734,46 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } // Download! - [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL - callbackQueue:dispatch_get_main_queue() - downloadProgressBlock:downloadProgressBlock - completion:^(CGImageRef coreGraphicsImage, NSError *error) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); - completionBlock(downloadedImage, error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }]]; + if (_downloaderSupportsNewProtocol) { + [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL + callbackQueue:dispatch_get_main_queue() + downloadProgress:downloadProgressBlock + completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) { + // We dereference iVars directly, so we can't have weakSelf going nil on us. + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + ASDN::MutexLocker l(_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (![_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + return; + } + + completionBlock(downloadedImage, error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }]]; + } else { + [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL + callbackQueue:dispatch_get_main_queue() + downloadProgressBlock:downloadProgressBlock + completion:^(CGImageRef coreGraphicsImage, NSError *error) { + // We dereference iVars directly, so we can't have weakSelf going nil on us. + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); + completionBlock(downloadedImage, error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }]]; + } } #pragma mark - diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 82513a8c55..be08fa2158 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -45,19 +45,19 @@ NS_ASSUME_NONNULL_BEGIN /** * The delegate, which must conform to the protocol. */ -@property (atomic, weak, readwrite) id delegate; +@property (nonatomic, weak, readwrite) id delegate; /** * A placeholder image to display while the URL is loading. */ -@property (nullable, atomic, strong, readwrite) UIImage *defaultImage; +@property (nullable, nonatomic, strong, readwrite) UIImage *defaultImage; /** * The URL of a new image to download and display. * * @discussion Changing this property will reset the displayed image to a placeholder () while loading. */ -@property (nullable, atomic, strong, readwrite) NSURL *URL; +@property (nullable, nonatomic, strong, readwrite) NSURL *URL; /** * Download and display a new image. diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 5c5c8f37be..ee350ef0e8 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -14,6 +14,10 @@ #import "ASEqualityHelpers.h" #import "ASThread.h" +#if PIN_REMOTE_IMAGE +#import "ASPINRemoteImageDownloader.h" +#endif + @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; @@ -27,9 +31,15 @@ UIImage *_defaultImage; NSUUID *_cacheUUID; - id _imageDownload; + id _downloadIdentifier; BOOL _imageLoaded; + + //set on init only + BOOL _downloaderSupportsNewProtocol; + BOOL _downloaderImplementsSetProgress; + BOOL _downloaderImplementsSetPriority; + BOOL _cacherSupportsNewProtocol; } @end @@ -42,6 +52,18 @@ _cache = cache; _downloader = downloader; + + NSAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); + + _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] ? YES : NO; + + NSAssert([cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); + + _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)] ? YES : NO; + _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO; + + _cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO; + _shouldCacheImage = YES; self.shouldBypassEnsureDisplay = YES; @@ -50,7 +72,11 @@ - (instancetype)init { +#if PIN_REMOTE_IMAGE + return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; +#else return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; +#endif } - (void)dealloc @@ -129,6 +155,41 @@ [super displayWillStart]; [self fetchData]; + + if (self.image == nil) { + if (_downloaderImplementsSetPriority) { + { + ASDN::MutexLocker l(_lock); + if (_downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier]; + } + } + } + + if (_downloaderImplementsSetProgress) { + { + ASDN::MutexLocker l(_lock); + + if (_downloadIdentifier != nil) { + __weak __typeof__(self) weakSelf = self; + [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + ASDN::MutexLocker l(_lock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + return; + } + + strongSelf.image = progressImage; + } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; + } + } + } + } } - (void)clearFetchedData @@ -158,31 +219,44 @@ - (void)_cancelImageDownload { - if (!_imageDownload) { + if (!_downloadIdentifier) { return; } - [_downloader cancelImageDownloadForIdentifier:_imageDownload]; - _imageDownload = nil; + if (_downloadIdentifier) { + [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + } + _downloadIdentifier = nil; _cacheUUID = nil; } -- (void)_downloadImageWithCompletion:(void (^)(CGImageRef, NSError*))finished +- (void)_downloadImageWithCompletion:(void (^)(UIImage *image, NSError*, id downloadIdentifier))finished { - _imageDownload = [_downloader downloadImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - downloadProgressBlock:NULL - completion:^(CGImageRef responseImage, NSError *error) { - if (finished != NULL) { - finished(responseImage, error); - } - }]; + if (_downloaderSupportsNewProtocol) { + _downloadIdentifier = [_downloader downloadImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(image, error, downloadIdentifier); + } + }]; + } else { + _downloadIdentifier = [_downloader downloadImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + downloadProgressBlock:NULL + completion:^(CGImageRef responseImage, NSError *error) { + if (finished != NULL) { + finished([UIImage imageWithCGImage:responseImage], error, nil); + } + }]; + } } - (void)_lazilyLoadImageIfNecessary { - if (!_imageLoaded && _URL != nil && _imageDownload == nil) { + if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { if (_URL.isFileURL) { { ASDN::MutexLocker l(_lock); @@ -210,7 +284,7 @@ } } else { __weak __typeof__(self) weakSelf = self; - void (^finished)(CGImageRef, NSError *) = ^(CGImageRef responseImage, NSError *error) { + void (^finished)(UIImage *, NSError *, id downloadIdentifier) = ^(UIImage *responseImage, NSError *error, id downloadIdentifier) { __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { return; @@ -218,13 +292,18 @@ { ASDN::MutexLocker l(strongSelf->_lock); + + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + return; + } if (responseImage != NULL) { strongSelf->_imageLoaded = YES; - strongSelf.image = [UIImage imageWithCGImage:responseImage]; + strongSelf.image = responseImage; } - strongSelf->_imageDownload = nil; + strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; } @@ -241,22 +320,32 @@ NSUUID *cacheUUID = [NSUUID UUID]; _cacheUUID = cacheUUID; - void (^cacheCompletion)(CGImageRef) = ^(CGImageRef image) { + void (^cacheCompletion)(UIImage *) = ^(UIImage *image) { // If the cache UUID changed, that means this request was cancelled. if (![_cacheUUID isEqual:cacheUUID]) { return; } - + if (image == NULL && _downloader != nil) { [self _downloadImageWithCompletion:finished]; } else { - finished(image, NULL); + finished(image, NULL, nil); } }; - - [_cache fetchCachedImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - completion:cacheCompletion]; + + if (_cacherSupportsNewProtocol) { + [_cache cachedImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + completion:cacheCompletion]; + } else { + void (^oldCacheCompletion)(CGImageRef) = ^(CGImageRef image) { + cacheCompletion([UIImage imageWithCGImage:image]); + }; + + [_cache fetchCachedImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + completion:oldCacheCompletion]; + } } else { [self _downloadImageWithCompletion:finished]; } diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 73301919cc..983d5bc8bb 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -230,7 +230,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext - (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^)(CGFloat))downloadProgressBlock + downloadProgress:(void (^)(CGFloat))downloadProgressBlock completion:(void (^)(CGImageRef, NSError *))completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index ba04eaaa75..405fdeb378 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -6,51 +6,51 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import #import NS_ASSUME_NONNULL_BEGIN +typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); + @protocol ASImageCacheProtocol @required /** - @abstract Attempts to fetch an image with the given URL from the cache. - @param URL The URL of the image to retrieve from the cache. - @param callbackQueue The queue to call `completion` on. If this value is nil, @{ref completion} will be invoked on the - main-queue. - @param completion The block to be called when the cache has either hit or missed. - @param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise. - @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block - the calling thread as it is likely to be called from the main thread. + @deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead */ - (void)fetchCachedImageWithURL:(nullable NSURL *)URL callbackQueue:(nullable dispatch_queue_t)callbackQueue completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; +/** + @abstract Attempts to fetch an image with the given URL from the cache. + @param URL The URL of the image to retrieve from the cache. + @param callbackQueue The queue to call `completion` on. If this value is nil, @{ref completion} will be invoked on the + main-queue. + @param completion The block to be called when the cache has either hit or missed. + @param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise. + @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block + the calling thread as it is likely to be called from the main thread. + */ +- (void)cachedImageWithURL:(nullable NSURL *)URL + callbackQueue:(nullable dispatch_queue_t)callbackQueue + completion:(ASImageCacherCompletion)completion; + @end +typedef void(^ASImageDownloaderCompletion)(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); +typedef void(^ASImageDownloaderProgress)(CGFloat progress); +typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, id _Nullable downloadIdentifier); + +typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { + ASImageDownloaderPriorityNormal = 0, + ASImageDownloaderPriorityDisplay, +}; + @protocol ASImageDownloaderProtocol @required -/** - @abstract Downloads an image with the given URL. - @param URL The URL of the image to download. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. If this value is nil, both blocks - will be invoked on the main-queue. - @param downloadProgressBlock The block to be invoked when the download of `URL` progresses. - @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. - @param completion The block to be invoked when the download has completed, or has failed. - @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. - @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. - */ -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(nullable dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock - completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; /** @abstract Cancels an image download. @@ -58,7 +58,58 @@ NS_ASSUME_NONNULL_BEGIN `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. @discussion This method has no effect if `downloadIdentifier` is nil. */ -- (void)cancelImageDownloadForIdentifier:(nullable id)downloadIdentifier; +- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier; + +@optional + +//You must implement one of the two following methods + +/** + @deprecated This method is deprecated @see downloadImageWithURL:callbackQueue:downloadProgress:completion: instead +*/ +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(nullable dispatch_queue_t)callbackQueue + downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock + completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; + +/** + @abstract Downloads an image with the given URL. + @param URL The URL of the image to download. + @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. + @param downloadProgress The block to be invoked when the download of `URL` progresses. + @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. + @param completion The block to be invoked when the download has completed, or has failed. + @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. + @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. + @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. + @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must + retain the identifier if you wish to use it later. + */ +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(nullable ASImageDownloaderCompletion)completion; + + +/** + @abstract Sets block to be called when a progress image is available. + @param progressBlock The block to be invoked when the download has a progressive render of an image available. + @param callbackQueue The queue to call `progressBlock` on. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + */ +- (void)setProgressImageBlock:(nullable ASImageDownloaderProgressImage)progressBlock + callbackQueue:(dispatch_queue_t)callbackQueue + withDownloadIdentifier:(id)downloadIdentifier; + +/** + @abstract Called to indicate what priority an image should be downloaded at. + @param priority The priority at which the image should be downloaded. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + */ +- (void)setPriority:(ASImageDownloaderPriority)priority +withDownloadIdentifier:(id)downloadIdentifier; @end diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h new file mode 100644 index 0000000000..1a7850b2fa --- /dev/null +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h @@ -0,0 +1,16 @@ +// +// ASPINRemoteImageDownloader.h +// Pods +// +// Created by Garrett Moon on 2/5/16. +// +// + +#import +#import "ASImageProtocols.h" + +@interface ASPINRemoteImageDownloader : NSObject + ++ (instancetype)sharedDownloader; + +@end diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m new file mode 100644 index 0000000000..e74baa16b6 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -0,0 +1,97 @@ +// +// ASPINRemoteImageDownloader.m +// Pods +// +// Created by Garrett Moon on 2/5/16. +// +// + +#ifdef PIN_REMOTE_IMAGE +#import "ASPINRemoteImageDownloader.h" +#import +#import + +@implementation ASPINRemoteImageDownloader + ++ (instancetype)sharedDownloader +{ + static ASPINRemoteImageDownloader *sharedDownloader = nil; + static dispatch_once_t once = 0; + dispatch_once(&once, ^{ + sharedDownloader = [[ASPINRemoteImageDownloader alloc] init]; + }); + return sharedDownloader; +} + +#pragma mark ASImageProtocols + +- (void)fetchCachedImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + completion:(void (^)(CGImageRef imageFromCache))completion +{ + NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil]; + UIImage *image = [[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] objectForKey:key]; + + dispatch_async(callbackQueue, ^{ + completion([image CGImage]); + }); +} + +/** + @abstract Downloads an image with the given URL. + @param URL The URL of the image to download. + @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. If this value is nil, both blocks + will be invoked on the main-queue. + @param downloadProgressBlock The block to be invoked when the download of `URL` progresses. + @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. + @param completion The block to be invoked when the download has completed, or has failed. + @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. + @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. + @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. + @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must + retain the identifier if you wish to use it later. + */ +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock + completion:(void (^)(CGImageRef image, NSError * error, id downloadIdentifier))completion +{ + return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL completion:^(PINRemoteImageManagerResult *result) { + dispatch_async(callbackQueue, ^{ + completion([result.image CGImage], result.error, result.UUID); + }); + }]; +} + +/** + @abstract Cancels an image download. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + @discussion This method has no effect if `downloadIdentifier` is nil. + */ +- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier +{ + NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + [[PINRemoteImageManager sharedImageManager] cancelTaskWithUUID:downloadIdentifier]; +} + +- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier +{ + NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + + [[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) { + dispatch_async(callbackQueue, ^{ + progressBlock(result.image, result.UUID); + }); + } ofTaskWithUUID:downloadIdentifier]; +} + +- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier +{ + NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + + [[PINRemoteImageManager sharedImageManager] setPriority:PINRemoteImageManagerPriorityHigh ofTaskWithUUID:downloadIdentifier]; +} + +@end +#endif \ No newline at end of file diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m index 069b545d00..5a1d967787 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m @@ -145,14 +145,14 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)()) NSArray *URL; [inv getArgument:&URL atIndex:2]; - void (^completionBlock)(CGImageRef); + ASImageCacherCompletion completionBlock; [inv getArgument:&completionBlock atIndex:4]; // Call the completion block with our test image and URL. NSURL *testImageURL = [self _testImageURL]; XCTAssertEqualObjects(URL, testImageURL, @"Fetching URL other than test image"); - completionBlock([self _testImage].CGImage); - }] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]]; + completionBlock([self _testImage]); + }] cachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]]; imageNode.imageIdentifiers = @[imageIdentifier]; // Kick off loading. @@ -302,25 +302,25 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)()) // Mock a cache miss. id mockCache = [OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)]; [[[mockCache stub] andDo:^(NSInvocation *inv) { - void (^completion)(CGImageRef imageFromCache); + ASImageCacherCompletion completion; [inv getArgument:&completion atIndex:4]; completion(nil); - }] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]]; + }] cachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]]; // Mock a 50%-progress URL download. id mockDownloader = [OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)]; const CGFloat mockedProgress = 0.5; [[[mockDownloader stub] andDo:^(NSInvocation *inv) { // Simulate progress. - void (^progressBlock)(CGFloat progress); + ASImageDownloaderProgress progressBlock; [inv getArgument:&progressBlock atIndex:4]; progressBlock(mockedProgress); // Simulate completion. - void (^completionBlock)(CGImageRef image, NSError *error); + ASImageDownloaderCompletion completionBlock; [inv getArgument:&completionBlock atIndex:5]; - completionBlock([self _testImage].CGImage, nil); - }] downloadImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] downloadProgressBlock:[OCMArg any] completion:[OCMArg any]]; + completionBlock([self _testImage], nil, nil); + }] downloadImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] downloadProgress:[OCMArg any] completion:[OCMArg any]]; ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader]; NSNumber *imageIdentifier = @1; diff --git a/examples/Multiplex/Sample/ScreenNode.m b/examples/Multiplex/Sample/ScreenNode.m index b73368a03f..02df45922e 100644 --- a/examples/Multiplex/Sample/ScreenNode.m +++ b/examples/Multiplex/Sample/ScreenNode.m @@ -20,7 +20,7 @@ } // multiplex image node! - // NB: we're using a custom downloader with an artificial delay for this demo, but ASBasicImageDownloader works too! + // NB: we're using a custom downloader with an artificial delay for this demo, but ASPINRemoteImageDownloader works too! _imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self]; _imageNode.dataSource = self; _imageNode.delegate = self; From 0be51c1ca2abfd04cb824db488e16ce21c550e44 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 8 Feb 2016 16:30:34 -0800 Subject: [PATCH 074/224] Leave ASBasicImageDownloader alone. --- AsyncDisplayKit/Details/ASBasicImageDownloader.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 983d5bc8bb..73301919cc 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -230,7 +230,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext - (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(void (^)(CGFloat))downloadProgressBlock + downloadProgressBlock:(void (^)(CGFloat))downloadProgressBlock completion:(void (^)(CGImageRef, NSError *))completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; From bdb486cf9e257d65cccf2ac8afed60de488d9549 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 9 Feb 2016 06:56:34 -0800 Subject: [PATCH 075/224] Fix sample build errors --- examples/ASTableViewStressTest/Sample/AppDelegate.m | 2 -- .../VerticalWithinHorizontalScrolling/Sample/AppDelegate.m | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.m b/examples/ASTableViewStressTest/Sample/AppDelegate.m index 4bc15faea9..50382b9daa 100644 --- a/examples/ASTableViewStressTest/Sample/AppDelegate.m +++ b/examples/ASTableViewStressTest/Sample/AppDelegate.m @@ -20,8 +20,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [ASDisplayNode setShouldUseNewRenderingRange:YES]; - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[UINavigationController alloc] init]; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m b/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m index 3ba9d1faf9..6c998ed4bb 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m @@ -19,9 +19,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - [ASDisplayNode setShouldUseNewRenderingRange:YES]; - +{ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; From 820390e4967797cdbb7df8d25ad3a4ee63a46e54 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 9 Feb 2016 10:31:10 -0800 Subject: [PATCH 076/224] Fix build and podfile --- AsyncDisplayKit.podspec | 2 +- AsyncDisplayKit/Details/ASImageProtocols.h | 2 +- AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 9b47f55c84..263aee5aa6 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |spec| spec.subspec 'PINRemoteImage' do |pin| pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage' + pin.dependency 'PINRemoteImage', '>= 2' pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' end diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index 405fdeb378..4f1abd65e6 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -15,7 +15,7 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); @protocol ASImageCacheProtocol -@required +@optional /** @deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead */ diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index e74baa16b6..d88bbea19f 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -53,12 +53,12 @@ */ - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock - completion:(void (^)(CGImageRef image, NSError * error, id downloadIdentifier))completion + downloadProgress:(void (^)(CGFloat progress))downloadProgressBlock + completion:(void (^)(UIImage *image, NSError * error, id downloadIdentifier))completion { return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL completion:^(PINRemoteImageManagerResult *result) { dispatch_async(callbackQueue, ^{ - completion([result.image CGImage], result.error, result.UUID); + completion(result.image, result.error, result.UUID); }); }]; } From 48fc4810cdfffaa67b4e4f5fcc77c50d9c2ec5dd Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 9 Feb 2016 14:05:36 -0800 Subject: [PATCH 077/224] Addressing comments --- AsyncDisplayKit.podspec | 2 +- AsyncDisplayKit/ASMultiplexImageNode.mm | 119 ++++++++++-------- AsyncDisplayKit/ASNetworkImageNode.h | 6 +- AsyncDisplayKit/ASNetworkImageNode.mm | 107 +++++++++------- AsyncDisplayKit/Details/ASImageProtocols.h | 57 ++++++--- .../Details/ASPINRemoteImageDownloader.m | 57 ++++----- 6 files changed, 200 insertions(+), 148 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 263aee5aa6..4872c47f84 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |spec| spec.subspec 'PINRemoteImage' do |pin| pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage', '>= 2' + pin.dependency 'PINRemoteImage/iOS', '>= 2' pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' end diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 526d1be1ef..c5e33cbcab 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -48,8 +48,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent { @private // Core. - id _cache; - id _downloader; + id _cache; + id _downloader; __weak id _delegate; struct { @@ -85,7 +85,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent BOOL _downloaderSupportsNewProtocol; BOOL _downloaderImplementsSetProgress; BOOL _downloaderImplementsSetPriority; - BOOL _cacherSupportsNewProtocol; + BOOL _cacheSupportsNewProtocol; + BOOL _cacheSupportsClearing; } //! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. @@ -156,13 +157,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent */ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; -/** - @abstract Returns a Boolean value indicating whether the downloaded image should be removed when clearing fetched data - @discussion Downloaded image data should only be cleared out if a cache is present - @return YES if an image cache is available; otherwise, NO. - */ -- (BOOL)_shouldClearFetchedImageData; - @end @implementation ASMultiplexImageNode @@ -176,16 +170,17 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; - NSAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); + ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); - _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] ? YES : NO; + _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; - NSAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); + ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); - _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)] ? YES : NO; - _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO; + _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; + _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO; + _cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; + _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; self.shouldBypassEnsureDisplay = YES; @@ -220,16 +215,17 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent { [super clearFetchedData]; - if ([self _shouldClearFetchedImageData]) { - - [_phImageRequestOperation cancel]; + [_phImageRequestOperation cancel]; - [self _setDownloadIdentifier:nil]; - - // setting this to nil makes the node fetch images the next time its display starts - _loadedImageIdentifier = nil; - self.image = nil; + [self _setDownloadIdentifier:nil]; + + if (_cacheSupportsClearing) { + [_cache clearFetchedImageFromCacheWithURL:[_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]]; } + + // setting this to nil makes the node fetch images the next time its display starts + _loadedImageIdentifier = nil; + self.image = nil; } - (void)fetchData @@ -266,6 +262,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } } +/* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary + in ASNetworkImageNode as well. */ - (void)displayWillStart { [super displayWillStart]; @@ -276,34 +274,53 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent { ASDN::MutexLocker l(_downloadIdentifierLock); if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier]; + [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier]; + } + } + } +} + +/* visibilityDidChange in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary + in ASNetworkImageNode as well. */ +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + + if (_downloaderImplementsSetPriority) { + ASDN::MutexLocker l(_downloadIdentifierLock); + if (_downloadIdentifier != nil) { + if (isVisible) { + [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; + } else { + [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; } } } - if (self.image == nil) { - if (_downloaderImplementsSetProgress) { - { - ASDN::MutexLocker l(_downloadIdentifierLock); - - if (_downloadIdentifier != nil) { - __weak __typeof__(self) weakSelf = self; - [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { - return; - } - - strongSelf.image = progressImage; - } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; - } + if (_downloaderImplementsSetProgress) { + ASDN::MutexLocker l(_downloadIdentifierLock); + + if (_downloadIdentifier != nil) { + __weak __typeof__(self) weakSelf = self; + ASImageDownloaderProgressImage progress = nil; + if (isVisible) { + progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + strongSelf.image = progressImage; + }; } + + [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; } } } @@ -695,7 +712,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); if (_cache) { - if (_cacherSupportsNewProtocol) { + if (_cacheSupportsNewProtocol) { [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(UIImage *imageFromCache) { completionBlock(imageFromCache); }]; @@ -746,7 +763,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent ASDN::MutexLocker l(_downloadIdentifierLock); //Getting a result back for a different download identifier, download must not have been successfully canceled - if (![_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } @@ -811,10 +828,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent [self _loadNextImage]; } -- (BOOL)_shouldClearFetchedImageData { - return _cache != nil; -} - @end #if TARGET_OS_IOS @implementation NSURL (ASPhotosFrameworkURLs) diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index be08fa2158..82513a8c55 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -45,19 +45,19 @@ NS_ASSUME_NONNULL_BEGIN /** * The delegate, which must conform to the protocol. */ -@property (nonatomic, weak, readwrite) id delegate; +@property (atomic, weak, readwrite) id delegate; /** * A placeholder image to display while the URL is loading. */ -@property (nullable, nonatomic, strong, readwrite) UIImage *defaultImage; +@property (nullable, atomic, strong, readwrite) UIImage *defaultImage; /** * The URL of a new image to download and display. * * @discussion Changing this property will reset the displayed image to a placeholder () while loading. */ -@property (nullable, nonatomic, strong, readwrite) NSURL *URL; +@property (nullable, atomic, strong, readwrite) NSURL *URL; /** * Download and display a new image. diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index ee350ef0e8..c91f3b2960 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -21,8 +21,8 @@ @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; - __weak id _cache; - __weak id _downloader; + __weak id _cache; + __weak id _downloader; // Only access any of these with _lock. __weak id _delegate; @@ -39,7 +39,8 @@ BOOL _downloaderSupportsNewProtocol; BOOL _downloaderImplementsSetProgress; BOOL _downloaderImplementsSetPriority; - BOOL _cacherSupportsNewProtocol; + BOOL _cacheSupportsNewProtocol; + BOOL _cacheSupportsClearing; } @end @@ -53,16 +54,17 @@ _cache = cache; _downloader = downloader; - NSAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); + ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); - _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] ? YES : NO; + _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; - NSAssert([cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); + ASDisplayNodeAssert([cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); - _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)] ? YES : NO; - _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO; + _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; + _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO; + _cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; + _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _shouldCacheImage = YES; self.shouldBypassEnsureDisplay = YES; @@ -150,44 +152,60 @@ return _delegate; } +/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary + in ASMultiplexImageNode as well. */ - (void)displayWillStart { [super displayWillStart]; [self fetchData]; - if (self.image == nil) { - if (_downloaderImplementsSetPriority) { - { - ASDN::MutexLocker l(_lock); - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier]; - } + if (self.image == nil && _downloaderImplementsSetPriority) { + ASDN::MutexLocker l(_lock); + if (_downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier]; + } + } +} + +/* visibilityDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary + in ASMultiplexImageNode as well. */ +- (void)visibilityDidChange:(BOOL)isVisible +{ + if (_downloaderImplementsSetPriority) { + ASDN::MutexLocker l(_lock); + if (_downloadIdentifier != nil) { + if (isVisible) { + [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; + } else { + [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; } } + } + + if (_downloaderImplementsSetProgress) { + ASDN::MutexLocker l(_lock); - if (_downloaderImplementsSetProgress) { - { - ASDN::MutexLocker l(_lock); - - if (_downloadIdentifier != nil) { - __weak __typeof__(self) weakSelf = self; - [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - ASDN::MutexLocker l(_lock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { - return; - } - - strongSelf.image = progressImage; - } callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; - } + if (_downloadIdentifier != nil) { + __weak __typeof__(self) weakSelf = self; + ASImageDownloaderProgressImage progress = nil; + if (isVisible) { + progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) { + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + ASDN::MutexLocker l(_lock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + strongSelf.image = progressImage; + }; } + [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; } } } @@ -202,6 +220,9 @@ [self _cancelImageDownload]; self.image = _defaultImage; _imageLoaded = NO; + if (_cacheSupportsClearing) { + [_cache clearFetchedImageFromCacheWithURL:_URL]; + } } } @@ -294,7 +315,7 @@ ASDN::MutexLocker l(strongSelf->_lock); //Getting a result back for a different download identifier, download must not have been successfully canceled - if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) { + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } @@ -333,18 +354,16 @@ } }; - if (_cacherSupportsNewProtocol) { + if (_cacheSupportsNewProtocol) { [_cache cachedImageWithURL:_URL callbackQueue:dispatch_get_main_queue() completion:cacheCompletion]; } else { - void (^oldCacheCompletion)(CGImageRef) = ^(CGImageRef image) { - cacheCompletion([UIImage imageWithCGImage:image]); - }; - [_cache fetchCachedImageWithURL:_URL callbackQueue:dispatch_get_main_queue() - completion:oldCacheCompletion]; + completion:^(CGImageRef image) { + cacheCompletion([UIImage imageWithCGImage:image]); + }]; } } else { [self _downloadImageWithCompletion:finished]; diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index 4f1abd65e6..eb3690023f 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -16,12 +16,6 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); @protocol ASImageCacheProtocol @optional -/** - @deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead - */ -- (void)fetchCachedImageWithURL:(nullable NSURL *)URL - callbackQueue:(nullable dispatch_queue_t)callbackQueue - completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; /** @abstract Attempts to fetch an image with the given URL from the cache. @@ -33,10 +27,17 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block the calling thread as it is likely to be called from the main thread. */ -- (void)cachedImageWithURL:(nullable NSURL *)URL - callbackQueue:(nullable dispatch_queue_t)callbackQueue +- (void)cachedImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion; +/** + @abstract Called during clearFetchedData. Allows the cache to optionally trim items. + @note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful + if you have a memory and disk cache in which case you'll likely want to clear out the memory cache. + */ +- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL; + @end typedef void(^ASImageDownloaderCompletion)(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); @@ -44,8 +45,9 @@ typedef void(^ASImageDownloaderProgress)(CGFloat progress); typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, id _Nullable downloadIdentifier); typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { - ASImageDownloaderPriorityNormal = 0, - ASImageDownloaderPriorityDisplay, + ASImageDownloaderPriorityPreload = 0, + ASImageDownloaderPriorityImminent, + ASImageDownloaderPriorityVisible }; @protocol ASImageDownloaderProtocol @@ -62,15 +64,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @optional -//You must implement one of the two following methods - -/** - @deprecated This method is deprecated @see downloadImageWithURL:callbackQueue:downloadProgress:completion: instead -*/ -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(nullable dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock - completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; +//You must implement the following method OR the deprecated method at the bottom /** @abstract Downloads an image with the given URL. @@ -113,4 +107,29 @@ withDownloadIdentifier:(id)downloadIdentifier; @end +@protocol ASImageDownloaderProtocolDeprecated + +@optional +/** + @deprecated This method is deprecated @see downloadImageWithURL:callbackQueue:downloadProgress:completion: instead + */ +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(nullable dispatch_queue_t)callbackQueue + downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock + completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; + +@end + +@protocol ASImageCacheProtocolDeprecated + +@optional +/** + @deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead + */ +- (void)fetchCachedImageWithURL:(nullable NSURL *)URL + callbackQueue:(nullable dispatch_queue_t)callbackQueue + completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index d88bbea19f..0345981298 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -8,6 +8,9 @@ #ifdef PIN_REMOTE_IMAGE #import "ASPINRemoteImageDownloader.h" + +#import "ASAssert.h" + #import #import @@ -29,28 +32,18 @@ callbackQueue:(dispatch_queue_t)callbackQueue completion:(void (^)(CGImageRef imageFromCache))completion { - NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil]; - UIImage *image = [[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] objectForKey:key]; - + //We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. dispatch_async(callbackQueue, ^{ - completion([image CGImage]); + completion(nil); }); } -/** - @abstract Downloads an image with the given URL. - @param URL The URL of the image to download. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. If this value is nil, both blocks - will be invoked on the main-queue. - @param downloadProgressBlock The block to be invoked when the download of `URL` progresses. - @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. - @param completion The block to be invoked when the download has completed, or has failed. - @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. - @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. - */ +- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL +{ + NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil]; + [[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] removeObjectForKey:key]; +} + - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(void (^)(CGFloat progress))downloadProgressBlock @@ -63,21 +56,15 @@ }]; } -/** - @abstract Cancels an image download. - @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. - @discussion This method has no effect if `downloadIdentifier` is nil. - */ - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { - NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); [[PINRemoteImageManager sharedImageManager] cancelTaskWithUUID:downloadIdentifier]; } - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier { - NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); [[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) { dispatch_async(callbackQueue, ^{ @@ -88,9 +75,23 @@ - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier { - NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[PINRemoteImageManager sharedImageManager] setPriority:PINRemoteImageManagerPriorityHigh ofTaskWithUUID:downloadIdentifier]; + PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityMedium; + switch (priority) { + case ASImageDownloaderPriorityPreload: + pi_priority = PINRemoteImageManagerPriorityMedium; + break; + + case ASImageDownloaderPriorityImminent: + pi_priority = PINRemoteImageManagerPriorityHigh; + break; + + case ASImageDownloaderPriorityVisible: + pi_priority = PINRemoteImageManagerPriorityVeryHigh; + break; + } + [[PINRemoteImageManager sharedImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; } @end From 419c6038d79b5d72bd1b61e07f0c3cb5453327f6 Mon Sep 17 00:00:00 2001 From: Baraa Hamodi Date: Tue, 9 Feb 2016 14:20:23 -0800 Subject: [PATCH 078/224] Update Copyright License. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 30472d99ca..507edbd628 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ BSD License For AsyncDisplayKit software -Copyright (c) 2014, Facebook, Inc. All rights reserved. +Copyright (c) 2014-present, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: From 5c2690e7e9409f5b6f1477615b48787dd213c4e7 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 9 Feb 2016 14:24:05 -0800 Subject: [PATCH 079/224] set reformed progress block to nil if progress block is nil --- .../Details/ASPINRemoteImageDownloader.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 0345981298..49ab3f43e1 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -66,11 +66,15 @@ { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) { - dispatch_async(callbackQueue, ^{ - progressBlock(result.image, result.UUID); - }); - } ofTaskWithUUID:downloadIdentifier]; + if (progressBlock) { + [[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) { + dispatch_async(callbackQueue, ^{ + progressBlock(result.image, result.UUID); + }); + } ofTaskWithUUID:downloadIdentifier]; + } else { + [[PINRemoteImageManager sharedImageManager] setProgressCallback:nil ofTaskWithUUID:downloadIdentifier]; + } } - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier From a471c1921632aa96788c5e85a83110abeebce924 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 9 Feb 2016 22:35:35 -0800 Subject: [PATCH 080/224] [ASDisplayNode] Remove locking in -isNodeLoaded when called on main, as an optimization. Additionally, this is often needed in threading-critical cases where interaction of sophisticated 3rd-party code with framework internals like ASDataController may cause deadlocks. --- AsyncDisplayKit/ASDisplayNode.mm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b479b27498..0ce0bd9f6e 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -577,8 +577,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (BOOL)isNodeLoaded { - ASDN::MutexLocker l(_propertyLock); - return (_view != nil || (_flags.layerBacked && _layer != nil)); + if (ASDisplayNodeThreadIsMain()) { + // Because the view and layer can only be created and destroyed on Main, that is also the only thread + // where the state of this property can change. As an optimization, we can avoid locking. + return (_view != nil || (_layer != nil && _flags.layerBacked)); + } else { + ASDN::MutexLocker l(_propertyLock); + return (_view != nil || (_layer != nil && _flags.layerBacked)); + } } - (NSString *)name From 3e5daf4ccde39fdc6951805029ea1ecbadc3b2cf Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Tue, 9 Feb 2016 22:47:43 -0800 Subject: [PATCH 081/224] Update data source protocols to make synchronous node creation api methods optional. --- AsyncDisplayKit/ASCollectionView.h | 9 +++-- AsyncDisplayKit/ASCollectionView.mm | 17 ++------ AsyncDisplayKit/ASPagerNode.h | 39 ++++++++++++++++--- AsyncDisplayKit/ASPagerNode.m | 10 ++--- AsyncDisplayKit/ASTableView.h | 7 ++-- AsyncDisplayKit/ASTableView.mm | 17 +++----- .../Details/ASCollectionDataController.h | 4 +- .../Details/ASCollectionDataController.mm | 2 + AsyncDisplayKit/Details/ASDataController.h | 5 --- 9 files changed, 58 insertions(+), 52 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 3e01e095a4..6b4f8a38c6 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -340,6 +340,8 @@ NS_ASSUME_NONNULL_BEGIN #define ASCollectionViewDataSource ASCollectionDataSource @protocol ASCollectionDataSource +@optional + /** * Similar to -collectionView:cellForItemAtIndexPath:. * @@ -353,16 +355,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; - -@optional - /** + * Similar to -collectionView:nodeForItemAtIndexPath: + * This method takes precedence over collectionView:nodeForItemAtIndexPath: if implemented. * * @param collectionView The sender. * * @param indexPath The index path of the requested node. * - * @returns a block that creates the node for display at this indexpath. + * @returns a block that creates the node for display at this indexpath. * Must be thread-safe (can be called on the main thread or a background * queue) and should not implement reuse (it will be called once per row). */ diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 23ae37aec8..eda88bf79f 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -294,7 +294,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // super.dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes. super.dataSource = nil; - if (asyncDataSource == nil) { _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; @@ -305,6 +304,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _asyncDataSourceImplementsConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _asyncDataSourceImplementsNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; + + // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: + ASDisplayNodeAssertTrue(_asyncDataSourceImplementsNodeBlockForItemAtIndexPath || [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]); } super.dataSource = (id)_proxyDataSource; @@ -678,19 +680,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - ASDataControllerSource -- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - - ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); - if (node.layoutDelegate == nil) { - node.layoutDelegate = self; - } - return node; -} - - - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) { diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 1b36e45bfd..3596dfcad7 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -10,15 +10,44 @@ @class ASPagerNode; @protocol ASPagerNodeDataSource -// This method replaces -collectionView:numberOfItemsInSection: -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; -// This method replaces -collectionView:nodeForItemAtIndexPath: -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; +/** + * This method replaces -collectionView:numberOfItemsInSection: + * + * @param pagerNode The sender. + * + * + * @returns The total number of pages that can display in the pagerNode. + */ +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; @optional -// This method replaces -collectionView:nodeBlockForItemAtIndexPath: +/** + * This method replaces -collectionView:nodeForItemAtIndexPath: + * + * @param pagerNode The sender. + * + * @param index The index of the requested node. + * + * @returns a node for display at this index. This will be called on the main thread and should + * not implement reuse (it will be called once per row). Unlike UICollectionView's version, + * this method is not called when the row is about to display. + */ +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +/** + * This method replaces -collectionView:nodeBlockForItemAtIndexPath: + * This method takes precedence over pagerNode:nodeAtIndex: if implemented. + * + * @param pagerNode The sender. + * + * @param index The index of the requested node. + * + * @returns a block that creates the node for display at this index. + * Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). + */ - (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; @end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index bcd275cb18..4d7ee51f9f 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -83,13 +83,6 @@ #pragma mark - ASCollectionViewDataSource -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - ASCellNode *pageNode = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; - return pageNode; -} - - (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); @@ -123,7 +116,10 @@ if (pagerDataSource != _pagerDataSource) { _pagerDataSource = pagerDataSource; _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; + // Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex: + ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]); _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + super.dataSource = (id )_proxy; } } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index f7a51fc73d..b21d2ac94a 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -315,6 +315,8 @@ NS_ASSUME_NONNULL_BEGIN */ @protocol ASTableDataSource +@optional + /** * Similar to -tableView:cellForRowAtIndexPath:. * @@ -327,11 +329,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; -@optional /** - * Similar to -tableView:nodeForRowAtIndexPath:. - * + * Similar to -tableView:nodeForRowAtIndexPath: + * This method takes precedence over tableView:nodeForRowAtIndexPath: if implemented. * @param tableView The sender. * * @param indexPath The index path of the requested node. diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 28d8ef3de9..111e5afe18 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -111,6 +111,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL _ignoreNodesConstrainedWidthChange; BOOL _queuedNodeHeightUpdate; BOOL _isDeallocating; + BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -256,8 +257,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (asyncDataSource == nil) { _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; + _dataSourceImplementsNodeBlockForRowAtIndexPath = NO; } else { _asyncDataSource = asyncDataSource; + _dataSourceImplementsNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; + // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath: + ASDisplayNodeAssertTrue(_dataSourceImplementsNodeBlockForRowAtIndexPath || [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]); _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; } @@ -862,18 +867,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - ASDataControllerDelegate -- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - - ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); - if (node.layoutDelegate == nil) { - node.layoutDelegate = self; - } - return node; -} - - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { if (![_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]) { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 1ac859cf54..ec7a30635b 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -17,8 +17,6 @@ @protocol ASCollectionDataControllerSource -- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - /** The constrained size range for layout. */ @@ -32,6 +30,8 @@ @optional +- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + - (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; @end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 90b1be92cc..2408a57e1f 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -255,6 +255,8 @@ { [super setDataSource:dataSource]; _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + + ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); } @end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index ed1f453ad8..6648275d9b 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -35,11 +35,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @protocol ASDataControllerSource -/** - Fetch the ASCellNode for specific index path. - */ -- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath; - /** Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. */ From 9e53dcd4021678bd269993ee727c3766fef9ec27 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Wed, 10 Feb 2016 01:35:07 -0800 Subject: [PATCH 082/224] Fix allocatedNodes Subarray to match indexPaths subarray --- AsyncDisplayKit/Details/ASDataController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index ac480a91d8..ddc821f133 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -188,11 +188,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; } else { allocationBlock(); [_mainSerialQueue performBlockOnMainThread:^{ - [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; }]; } From 752f0540d0fad5a1ac5a684054ac4db64bdac3fd Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Wed, 10 Feb 2016 10:38:41 -0800 Subject: [PATCH 083/224] Fix warnings --- AsyncDisplayKit/ASMultiplexImageNode.mm | 4 ++-- AsyncDisplayKit/ASNetworkImageNode.mm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index c5e33cbcab..77acf900e7 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -167,8 +167,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent if (!(self = [super init])) return nil; - _cache = cache; - _downloader = downloader; + _cache = (id)cache; + _downloader = (id)downloader; ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index c91f3b2960..debeab01f7 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -51,8 +51,8 @@ if (!(self = [super init])) return nil; - _cache = cache; - _downloader = downloader; + _cache = (id)cache; + _downloader = (id)downloader; ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); From 9dc358196a4dafaeb7d5b1bceed4371423dfbee0 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Thu, 4 Feb 2016 17:02:55 -0800 Subject: [PATCH 084/224] Basic implementation of transitioning API for layout specs --- AsyncDisplayKit.xcodeproj/project.pbxproj | 14 ++ AsyncDisplayKit/ASContextTransitioning.h | 33 ++++ AsyncDisplayKit/ASDisplayNode+Subclasses.h | 19 ++ AsyncDisplayKit/ASDisplayNode.h | 11 ++ AsyncDisplayKit/ASDisplayNode.mm | 183 ++++++++++++------ AsyncDisplayKit/AsyncDisplayKit.h | 1 + .../Private/ASDisplayNodeInternal.h | 10 +- AsyncDisplayKit/_ASTransitionContext.h | 32 +++ AsyncDisplayKit/_ASTransitionContext.m | 49 +++++ 9 files changed, 289 insertions(+), 63 deletions(-) create mode 100644 AsyncDisplayKit/ASContextTransitioning.h create mode 100644 AsyncDisplayKit/_ASTransitionContext.h create mode 100644 AsyncDisplayKit/_ASTransitionContext.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 6fa2606571..34da2492a9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -460,6 +460,10 @@ CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; + DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; + DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; + DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; @@ -781,6 +785,9 @@ D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = _ASTransitionContext.h; path = ../_ASTransitionContext.h; sourceTree = ""; }; + DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = _ASTransitionContext.m; path = ../_ASTransitionContext.m; sourceTree = ""; }; + DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASContextTransitioning.h; sourceTree = ""; }; DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; @@ -959,6 +966,7 @@ ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, + DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, @@ -1102,6 +1110,8 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */, + DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */, @@ -1343,6 +1353,7 @@ DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */, + DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */, 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, 257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */, 058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */, @@ -1352,6 +1363,7 @@ 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, + DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */, ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, @@ -1501,6 +1513,7 @@ 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, @@ -1775,6 +1788,7 @@ ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */, ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, + DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, 9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h new file mode 100644 index 0000000000..0eb5d42bae --- /dev/null +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -0,0 +1,33 @@ +// +// ASContextTransitioning.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@protocol ASContextTransitioning + +/** + @abstract The frame for the given node before the transition began. + @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. + */ +- (CGRect)initialFrameForNode:(ASDisplayNode *)node; + +/** + @abstract The frame for the given node when the transition completes. + @discussion Returns CGRectNull if the node is no longer in the hierarchy after the transition. + */ +- (CGRect)finalFrameForNode:(ASDisplayNode *)node; + +- (NSArray *)sublayouts; + +/** + @abstract Invoke this method when the transition is completed in `transitionLayout:` + @discussion Passing NO to `didComplete` will set the original layout as the new layout. + */ +- (void)completeTransition:(BOOL)didComplete; + +@end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 699b6213ed..f9195853d5 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -14,6 +14,7 @@ #import @class ASLayoutSpec; +@protocol ASContextTransitioning; NS_ASSUME_NONNULL_BEGIN @@ -155,6 +156,24 @@ NS_ASSUME_NONNULL_BEGIN - (void)invalidateCalculatedLayout; +/** @name Layout Transitioning */ + +/** + @discussion Called right before new nodes are inserted. A great place to setup layer attributes before animation. + */ +- (void)willTransitionLayout:(id)context; + +/** + @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. + */ +- (void)transitionLayout:(id)context; + +/** + @discussion A place to clean up your nodes after the transition + */ +- (void)didCompleteTransitionLayout:(id)context; + + /** @name Drawing */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index ecb35b5a66..773ac7d7bf 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -571,6 +571,17 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASDisplayNode (Transitioning) + +/** + @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + + @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + */ +- (void)transitionLayoutWithAnimation:(BOOL)animated; + +@end + /** * Convenience methods for debugging. diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0ce0bd9f6e..2e80e73be0 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -84,7 +84,7 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS #define TIME_SCOPED(outVar) #endif -@interface ASDisplayNode () <_ASDisplayLayerDelegate> +@interface ASDisplayNode () <_ASDisplayLayerDelegate, _ASTransitionContextDelegate> @property (assign, nonatomic) BOOL implicitNodeHierarchyManagement; @@ -996,6 +996,119 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return CGRectApplyAffineTransform(rect, flattenedTransform); } +#pragma mark - Layout Transition + +- (void)transitionLayoutWithAnimation:(BOOL)animated +{ + [self transitionLayoutToFit:_constrainedSize animated:animated]; +} + +- (void)transitionLayoutToFit:(ASSizeRange)constrainedSize animated:(BOOL)animated +{ + [self invalidateCalculatedLayout]; + [self measureWithSizeRange:constrainedSize]; // Generate a new layout + [self __transitionLayoutWithAnimation:animated]; +} + +- (void)__transitionLayoutWithAnimation:(BOOL)animated +{ + _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; + + [self willTransitionLayout:_transitionContext]; + if ([_insertedSubnodes count]) { + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + } + _insertedSubnodes = @[]; + } + + [self transitionLayout:_transitionContext]; +} + +- (void)willTransitionLayout:(id)context +{ +} + +- (void)transitionLayout:(id)context +{ + for (ASLayout *subnodeLayout in [context sublayouts]) { + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; + } + + [context completeTransition:YES]; +} + +- (void)didCompleteTransitionLayout:(id)context +{ + if ([_deletedSubnodes count]) { + for (_ASDisplayNodePosition *position in _deletedSubnodes) { + [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; + } + _deletedSubnodes = @[]; + } +} + +- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout +{ + CGRect subnodeFrame = CGRectZero; + CGPoint adjustedOrigin = layout.position; + if (isfinite(adjustedOrigin.x) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.x = 0; + } + if (isfinite(adjustedOrigin.y) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.y = 0; + } + subnodeFrame.origin = adjustedOrigin; + + CGSize adjustedSize = layout.size; + if (isfinite(adjustedSize.width) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); + adjustedSize.width = 0; + } + if (isfinite(adjustedSize.height) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedSize.height = 0; + } + subnodeFrame.size = adjustedSize; + + return subnodeFrame; +} + +#pragma mark - _ASTransitionContextDelegate + +- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete +{ + [self didCompleteTransitionLayout:context]; + _transitionContext = nil; +} + +- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node +{ + for (ASDisplayNode *subnode in _subnodes) { + if (ASObjectIsEqual(node, subnode)) { + return node.frame; + } + } + return CGRectNull; +} + +- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node +{ + for (ASLayout *layout in _layout.sublayouts) { + if (ASObjectIsEqual(node, layout.layoutableObject)) { + return [self _adjustedFrameForLayout:layout]; + } + } + return CGRectNull; +} + +- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context +{ + return _layout.sublayouts; +} + #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer @@ -2053,77 +2166,27 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (!_flags.isMeasured) { return; } - - // Assume that _layout was flattened and is 1-level deep. - ASDisplayNode *subnode = nil; - CGRect subnodeFrame = CGRectZero; - for (ASLayout *subnodeLayout in _layout.sublayouts) { - if (![[self class] usesImplicitHierarchyManagement]) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); - } - CGPoint adjustedOrigin = subnodeLayout.position; - if (isfinite(adjustedOrigin.x) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.x = 0; - } - if (isfinite(adjustedOrigin.y) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.y = 0; - } - subnodeFrame.origin = adjustedOrigin; - - CGSize adjustedSize = subnodeLayout.size; - if (isfinite(adjustedSize.width) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); - adjustedSize.width = 0; - } - if (isfinite(adjustedSize.height) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedSize.height = 0; - } - subnodeFrame.size = adjustedSize; - - subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject); - [subnode setFrame:subnodeFrame]; - } if ([[self class] usesImplicitHierarchyManagement]) { - for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; - } - - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + [self __transitionLayoutWithAnimation:NO]; + } else { + // Assume that _layout was flattened and is 1-level deep. + CGRect subnodeFrame = CGRectZero; + for (ASLayout *subnodeLayout in _layout.sublayouts) { + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); + subnodeFrame = [self _adjustedFrameForLayout:subnodeLayout]; + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = subnodeFrame; } } } - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { - ASDisplayNodeAssertThreadAffinity(self); - - if (!_managedSubnodes) { - _managedSubnodes = [NSMutableArray array]; - } - - ASDisplayNodeAssert(idx <= [_managedSubnodes count], @"index needs to be in range of the current managed subnodes"); - if (idx == [_managedSubnodes count]) { - [_managedSubnodes addObject:node]; - } else { - [_managedSubnodes insertObject:node atIndex:idx]; - } - [self addSubnode:node]; + [self insertSubnode:node atIndex:idx]; } - (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { - ASDisplayNodeAssertThreadAffinity(self); - - if (!_managedSubnodes) { - _managedSubnodes = [NSMutableArray array]; - } - - [_managedSubnodes removeObjectAtIndex:idx]; [node removeFromSupernode]; } diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2ad55f9080..ca3dd3774f 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -68,3 +68,4 @@ #import #import #import +#import diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 668a5fb512..38e86b5731 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -17,6 +17,7 @@ #import "ASSentinel.h" #import "ASThread.h" #import "ASLayoutOptions.h" +#import "_ASTransitionContext.h" @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @@ -63,10 +64,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; - - // Subnodes implicitly managed by layout changes - NSMutableArray *_managedSubnodes; + _ASTransitionContext *_transitionContext; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; @@ -149,6 +148,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; +/** + Clamps the layout's origin or position to 0 if any of the calculated values are infinite. + */ +- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout; + // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. - (BOOL)__visibilityNotificationsDisabled; - (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h new file mode 100644 index 0000000000..2c964a99c5 --- /dev/null +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -0,0 +1,32 @@ +// +// _ASTransitionContext.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "ASContextTransitioning.h" + +@class ASLayout; +@class _ASTransitionContext; + +@protocol _ASTransitionContextDelegate + +- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; +- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; +- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; + +- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context; + +@end + +@interface _ASTransitionContext : NSObject + +@property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated; + +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; + +@end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m new file mode 100644 index 0000000000..e5cf012d26 --- /dev/null +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -0,0 +1,49 @@ +// +// _ASTransitionContext.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "_ASTransitionContext.h" + +@interface _ASTransitionContext () + +@property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate; + +@end + +@implementation _ASTransitionContext + +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate +{ + self = [super init]; + if (self) { + _animated = animated; + _delegate = delegate; + } + return self; +} + +- (CGRect)initialFrameForNode:(ASDisplayNode *)node +{ + return [_delegate transitionContext:self initialFrameForNode:node]; +} + +- (CGRect)finalFrameForNode:(ASDisplayNode *)node +{ + return [_delegate transitionContext:self finalFrameForNode:node]; +} + +- (NSArray *)sublayouts +{ + return [_delegate sublayoutsForTransitioningContext:self]; +} + +- (void)completeTransition:(BOOL)didComplete +{ + [_delegate transitionContext:self didComplete:didComplete]; +} + +@end From a2045f19c56a95d3da31701fce1fa70506d7e1d6 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Thu, 4 Feb 2016 17:22:47 -0800 Subject: [PATCH 085/224] Add test for complete reordering of LCS diff --- AsyncDisplayKitTests/ArrayDiffingTests.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m index d2224e0baa..636af90835 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -54,6 +54,12 @@ @[@0, @2], @[@1, @2, @3, @4], ], + @[ + @[@"bob", @"alice", @"dave", @"judy"], + @[@"judy", @"dave", @"alice", @"bob"], + @[@1, @2, @3], + @[@0, @1, @2], + ], ]; for (NSArray *test in tests) { From 8f3788d0b27aa8e590ea7ae0646fcb273133b66e Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 5 Feb 2016 11:15:40 -0800 Subject: [PATCH 086/224] Update LCS diffing to support insertions before deletions --- AsyncDisplayKit/Private/NSArray+Diffing.m | 40 +++++++++++++---------- AsyncDisplayKitTests/ArrayDiffingTests.m | 6 ++-- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m index 00893d1416..c9fd398361 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -21,29 +21,33 @@ { NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison]; - if (insertions) { - NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; - NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; - for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { - if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { - i++; j++; - } else { - [insertionIndexes addIndex:j]; - j++; - } + NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; + NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; + for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { + if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { + i++; j++; + } else { + [insertionIndexes addIndex:j]; + j++; } - *insertions = insertionIndexes; } - - if (deletions) { - NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet]; - for (NSInteger i = 0; i < self.count; i++) { - if (![commonIndexes containsIndex:i]) { - [deletionIndexes addIndex:i]; + *insertions = insertionIndexes; + + NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet]; + NSInteger offset = 0; + NSInteger insertionIndex = -1; + for (NSInteger i = 0; i < self.count; i++) { + if (![commonIndexes containsIndex:i]) { + // Offset deletions such that insertions are performed first + NSInteger j = [insertionIndexes indexLessThanOrEqualToIndex:i]; + if (j != NSNotFound && insertionIndex < j) { + offset++; + insertionIndex = j; } + [deletionIndexes addIndex:i + offset]; } - *deletions = deletionIndexes; } + *deletions = deletionIndexes; } - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m index 636af90835..a96e2b5207 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -46,19 +46,19 @@ @[@"bob", @"alice", @"dave"], @[@"gary", @"alice", @"dave", @"jack"], @[@0, @3], - @[@0], + @[@1], ], @[ @[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"], @[@"gary", @"bob", @"suzy", @"tony"], @[@0, @2], - @[@1, @2, @3, @4], + @[@2, @4, @5, @6], ], @[ @[@"bob", @"alice", @"dave", @"judy"], @[@"judy", @"dave", @"alice", @"bob"], @[@1, @2, @3], - @[@0, @1, @2], + @[@0, @2, @4], ], ]; From 6a2903f2ec3f7454d9a4c1460a87439c67771c83 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 5 Feb 2016 11:58:23 -0800 Subject: [PATCH 087/224] Revert "Update LCS diffing to support insertions before deletions" This reverts commit 8d90f1bccda0b7d99639085e0bfa3488c3c01dbe. --- AsyncDisplayKit/Private/NSArray+Diffing.m | 44 +++++++++++------------ AsyncDisplayKitTests/ArrayDiffingTests.m | 6 ++-- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Private/NSArray+Diffing.m index c9fd398361..00893d1416 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Private/NSArray+Diffing.m @@ -21,33 +21,29 @@ { NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison]; - NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; - NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; - for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { - if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { - i++; j++; - } else { - [insertionIndexes addIndex:j]; - j++; - } - } - *insertions = insertionIndexes; - - NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet]; - NSInteger offset = 0; - NSInteger insertionIndex = -1; - for (NSInteger i = 0; i < self.count; i++) { - if (![commonIndexes containsIndex:i]) { - // Offset deletions such that insertions are performed first - NSInteger j = [insertionIndexes indexLessThanOrEqualToIndex:i]; - if (j != NSNotFound && insertionIndex < j) { - offset++; - insertionIndex = j; + if (insertions) { + NSArray *commonObjects = [self objectsAtIndexes:commonIndexes]; + NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet]; + for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) { + if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) { + i++; j++; + } else { + [insertionIndexes addIndex:j]; + j++; } - [deletionIndexes addIndex:i + offset]; } + *insertions = insertionIndexes; + } + + if (deletions) { + NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet]; + for (NSInteger i = 0; i < self.count; i++) { + if (![commonIndexes containsIndex:i]) { + [deletionIndexes addIndex:i]; + } + } + *deletions = deletionIndexes; } - *deletions = deletionIndexes; } - (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison diff --git a/AsyncDisplayKitTests/ArrayDiffingTests.m b/AsyncDisplayKitTests/ArrayDiffingTests.m index a96e2b5207..636af90835 100644 --- a/AsyncDisplayKitTests/ArrayDiffingTests.m +++ b/AsyncDisplayKitTests/ArrayDiffingTests.m @@ -46,19 +46,19 @@ @[@"bob", @"alice", @"dave"], @[@"gary", @"alice", @"dave", @"jack"], @[@0, @3], - @[@1], + @[@0], ], @[ @[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"], @[@"gary", @"bob", @"suzy", @"tony"], @[@0, @2], - @[@2, @4, @5, @6], + @[@1, @2, @3, @4], ], @[ @[@"bob", @"alice", @"dave", @"judy"], @[@"judy", @"dave", @"alice", @"bob"], @[@1, @2, @3], - @[@0, @2, @4], + @[@0, @1, @2], ], ]; From 3b1a32c4135f70cff4e6f8d0011060f0ee68ddff Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 5 Feb 2016 15:35:30 -0800 Subject: [PATCH 088/224] Clean up implicit hierarchy management to enable custom animation --- AsyncDisplayKit/ASContextTransitioning.h | 11 +- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 11 +- AsyncDisplayKit/ASDisplayNode.mm | 169 +++++++++++------- AsyncDisplayKit/AsyncDisplayKit.h | 2 +- .../Private/ASDisplayNodeInternal.h | 1 + AsyncDisplayKit/_ASTransitionContext.h | 2 - AsyncDisplayKit/_ASTransitionContext.m | 5 - 7 files changed, 117 insertions(+), 84 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 0eb5d42bae..37f2ae6b98 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -6,10 +6,15 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import +#import @protocol ASContextTransitioning +/** + @abstreact Defines if the given transition is animated + */ +- (BOOL)isAnimated; + /** @abstract The frame for the given node before the transition began. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. @@ -22,10 +27,8 @@ */ - (CGRect)finalFrameForNode:(ASDisplayNode *)node; -- (NSArray *)sublayouts; - /** - @abstract Invoke this method when the transition is completed in `transitionLayout:` + @abstract Invoke this method when the transition is completed in `animateLayoutTransition:` @discussion Passing NO to `didComplete` will set the original layout as the new layout. */ - (void)completeTransition:(BOOL)didComplete; diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index f9195853d5..e571503b81 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -12,9 +12,9 @@ #import #import #import +#import @class ASLayoutSpec; -@protocol ASContextTransitioning; NS_ASSUME_NONNULL_BEGIN @@ -158,20 +158,15 @@ NS_ASSUME_NONNULL_BEGIN /** @name Layout Transitioning */ -/** - @discussion Called right before new nodes are inserted. A great place to setup layer attributes before animation. - */ -- (void)willTransitionLayout:(id)context; - /** @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. */ -- (void)transitionLayout:(id)context; +- (void)animateLayoutTransition:(id)context; /** @discussion A place to clean up your nodes after the transition */ -- (void)didCompleteTransitionLayout:(id)context; +- (void)didCompleteLayoutTransition:(id)context; /** @name Drawing */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2e80e73be0..ce60b8cc01 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -658,12 +658,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; - _deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions offsetIndexes:insertions]; } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; - _deletedSubnodes = @[]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; + _deletedSubnodes = nil; + } + + if (!_deferImmediateHierarchyManagement) { + [self __implicitlyInsertSubnodes]; + [self __implicitlyRemoveSubnodes]; } } @@ -693,14 +698,43 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _layout; } -- (NSArray<_ASDisplayNodePosition *> *)_filterNodesInLayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes +/** + @abstract Retrieves nodes at the given indexes from the layout's sublayouts + */ +- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes { NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; - [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject; + NSInteger idx = [indexes firstIndex]; + while (idx != NSNotFound) { + ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; - }]; + idx = [indexes indexGreaterThanIndex:idx]; + } + return result; +} + +/** + @abstract Retrieves nodes at the given indexes from the layout's sublayouts, shifting the target index such that insertions can happen before deletions + */ +- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes offsetIndexes:(NSIndexSet *)offsets +{ + NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; + NSInteger offset = 0; + NSInteger offsetIndex = -1; + NSInteger idx = [indexes firstIndex]; + while (idx != NSNotFound) { + ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; + ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); + // Offset deletions such that insertions can be performed first + NSInteger j = [offsets indexLessThanOrEqualToIndex:offsetIndex]; + if (j != NSNotFound && offsetIndex < j) { + offset++; + offsetIndex = j; + } + [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx + offset]]; + idx = [indexes indexGreaterThanIndex:idx]; + } return result; } @@ -1000,82 +1034,58 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (void)transitionLayoutWithAnimation:(BOOL)animated { - [self transitionLayoutToFit:_constrainedSize animated:animated]; + [self transitionLayoutThatFits:_constrainedSize animated:animated]; } -- (void)transitionLayoutToFit:(ASSizeRange)constrainedSize animated:(BOOL)animated +- (void)transitionLayoutThatFits:(ASSizeRange)constrainedSize animated:(BOOL)animated { [self invalidateCalculatedLayout]; + _deferImmediateHierarchyManagement = YES; [self measureWithSizeRange:constrainedSize]; // Generate a new layout + _deferImmediateHierarchyManagement = NO; [self __transitionLayoutWithAnimation:animated]; } - (void)__transitionLayoutWithAnimation:(BOOL)animated { _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; - - [self willTransitionLayout:_transitionContext]; - if ([_insertedSubnodes count]) { - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self _implicitlyInsertSubnode:position.node atIndex:position.index]; - } - _insertedSubnodes = @[]; - } - - [self transitionLayout:_transitionContext]; + [self __implicitlyInsertSubnodes]; + [self animateLayoutTransition:_transitionContext]; } -- (void)willTransitionLayout:(id)context +- (void)animateLayoutTransition:(id)context { -} - -- (void)transitionLayout:(id)context -{ - for (ASLayout *subnodeLayout in [context sublayouts]) { - ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; - } - + [self __layoutSublayouts]; [context completeTransition:YES]; } - (void)didCompleteTransitionLayout:(id)context +{ + [self __implicitlyRemoveSubnodes]; +} + +#pragma mark - Implicit node hierarchy managagment + +- (void)__implicitlyInsertSubnodes +{ + if ([_insertedSubnodes count]) { + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + } + _insertedSubnodes = nil; + } +} + +- (void)__implicitlyRemoveSubnodes { if ([_deletedSubnodes count]) { for (_ASDisplayNodePosition *position in _deletedSubnodes) { [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; } - _deletedSubnodes = @[]; + _deletedSubnodes = nil; } } -- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout -{ - CGRect subnodeFrame = CGRectZero; - CGPoint adjustedOrigin = layout.position; - if (isfinite(adjustedOrigin.x) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.x = 0; - } - if (isfinite(adjustedOrigin.y) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.y = 0; - } - subnodeFrame.origin = adjustedOrigin; - - CGSize adjustedSize = layout.size; - if (isfinite(adjustedSize.width) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); - adjustedSize.width = 0; - } - if (isfinite(adjustedSize.height) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedSize.height = 0; - } - subnodeFrame.size = adjustedSize; - - return subnodeFrame; -} - #pragma mark - _ASTransitionContextDelegate - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete @@ -1104,11 +1114,6 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return CGRectNull; } -- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context -{ - return _layout.sublayouts; -} - #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer @@ -2168,7 +2173,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } if ([[self class] usesImplicitHierarchyManagement]) { - [self __transitionLayoutWithAnimation:NO]; + [self __layoutSublayouts]; } else { // Assume that _layout was flattened and is 1-level deep. CGRect subnodeFrame = CGRectZero; @@ -2180,6 +2185,42 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } +- (void)__layoutSublayouts +{ + for (ASLayout *subnodeLayout in _layout.sublayouts) { + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; + } +} + +- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout +{ + CGRect subnodeFrame = CGRectZero; + CGPoint adjustedOrigin = layout.position; + if (isfinite(adjustedOrigin.x) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.x = 0; + } + if (isfinite(adjustedOrigin.y) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.y = 0; + } + subnodeFrame.origin = adjustedOrigin; + + CGSize adjustedSize = layout.size; + if (isfinite(adjustedSize.width) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); + adjustedSize.width = 0; + } + if (isfinite(adjustedSize.height) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedSize.height = 0; + } + subnodeFrame.size = adjustedSize; + + NSLog(@"Adjusted frame: %@", NSStringFromCGRect(subnodeFrame)); + return subnodeFrame; +} + - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { [self insertSubnode:node atIndex:idx]; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index ca3dd3774f..9affcc7b37 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -54,6 +54,7 @@ #import #import #import +#import #import #import #import @@ -68,4 +69,3 @@ #import #import #import -#import diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 38e86b5731..336069e62c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -66,6 +66,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSMutableArray *_subnodes; _ASTransitionContext *_transitionContext; + BOOL _deferImmediateHierarchyManagement; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index 2c964a99c5..ab396514cb 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -19,8 +19,6 @@ - (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; - (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; -- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context; - @end @interface _ASTransitionContext : NSObject diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index e5cf012d26..059138e6ae 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -36,11 +36,6 @@ return [_delegate transitionContext:self finalFrameForNode:node]; } -- (NSArray *)sublayouts -{ - return [_delegate sublayoutsForTransitioningContext:self]; -} - - (void)completeTransition:(BOOL)didComplete { [_delegate transitionContext:self didComplete:didComplete]; From 51977ed162e5d9a83eae323404037a0fdb4705de Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 5 Feb 2016 16:14:20 -0800 Subject: [PATCH 089/224] Identify flattened layouts to allow filtering of non-hierarchy nodes --- AsyncDisplayKit/ASDisplayNode.mm | 21 +++++++++++---------- AsyncDisplayKit/Layout/ASLayout.h | 20 +++++++++++++++++++- AsyncDisplayKit/Layout/ASLayout.mm | 19 +++++++++++++++---- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ce60b8cc01..302e7ed9c3 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -655,6 +655,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if ([[self class] usesImplicitHierarchyManagement]) { if (_layout) { NSIndexSet *insertions, *deletions; + // TODO: Filter the flattened layouts, since it's including nodes that are not in the hierarchy [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; @@ -1086,6 +1087,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo } } +- (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx +{ + [self insertSubnode:node atIndex:idx]; +} + +- (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx +{ + [node removeFromSupernode]; +} + #pragma mark - _ASTransitionContextDelegate - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete @@ -2221,16 +2232,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return subnodeFrame; } -- (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx -{ - [self insertSubnode:node atIndex:idx]; -} - -- (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx -{ - [node removeFromSupernode]; -} - - (void)displayWillStart { // in case current node takes longer to display than it's subnodes, treat it as a dependent node diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 4619269d12..23545cebf2 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -34,6 +34,11 @@ extern BOOL CGPointIsNull(CGPoint point); */ @property (nonatomic, readonly) NSArray *sublayouts; +/** + Whether the current layout has been flattened + */ +@property (nonatomic, readonly) BOOL flattened; + /** * Initializer. * @@ -48,7 +53,8 @@ extern BOOL CGPointIsNull(CGPoint point); + (instancetype)layoutWithLayoutableObject:(id)layoutableObject size:(CGSize)size position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts; + sublayouts:(nullable NSArray *)sublayouts + flattened:(BOOL)flattened; /** * Convenience initializer that has CGPointNull position. @@ -77,6 +83,18 @@ extern BOOL CGPointIsNull(CGPoint point); */ + (instancetype)layoutWithLayoutableObject:(id)layoutableObject size:(CGSize)size; +/** + * Convenience initializer that is flattened and has CGPointNull position. + * + * @param layoutableObject The backing ASLayoutable object. + * + * @param size The size of this layout. + * + * @param sublayouts Sublayouts belong to the new layout. + */ ++ (instancetype)flattenedLayoutWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + sublayouts:(nullable NSArray *)sublayouts; /** * @abstract Evaluates a given predicate block against each object in the receiving layout tree diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index e02e618c7a..02ae752588 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -27,6 +27,7 @@ extern BOOL CGPointIsNull(CGPoint point) size:(CGSize)size position:(CGPoint)position sublayouts:(NSArray *)sublayouts + flattened:(BOOL)flattened { ASDisplayNodeAssert(layoutableObject, @"layoutableObject is required."); #if DEBUG @@ -45,6 +46,7 @@ extern BOOL CGPointIsNull(CGPoint point) l->_position = position; } l->_sublayouts = [sublayouts copy]; + l->_flattened = flattened; } return l; } @@ -53,7 +55,7 @@ extern BOOL CGPointIsNull(CGPoint point) size:(CGSize)size sublayouts:(NSArray *)sublayouts { - return [self layoutWithLayoutableObject:layoutableObject size:size position:CGPointNull sublayouts:sublayouts]; + return [self layoutWithLayoutableObject:layoutableObject size:size position:CGPointNull sublayouts:sublayouts flattened:NO]; } + (instancetype)layoutWithLayoutableObject:(id)layoutableObject size:(CGSize)size @@ -61,6 +63,13 @@ extern BOOL CGPointIsNull(CGPoint point) return [self layoutWithLayoutableObject:layoutableObject size:size sublayouts:nil]; } ++ (instancetype)flattenedLayoutWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + sublayouts:(nullable NSArray *)sublayouts +{ + return [self layoutWithLayoutableObject:layoutableObject size:size position:CGPointNull sublayouts:sublayouts flattened:YES]; +} + - (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *))predicateBlock { NSMutableArray *flattenedSublayouts = [NSMutableArray array]; @@ -69,6 +78,7 @@ extern BOOL CGPointIsNull(CGPoint point) ASLayout *layout; CGPoint absolutePosition; BOOL visited; + BOOL flattened; }; // Stack of Contexts, used to keep track of sublayouts while traversing this layout in a BFS fashion. @@ -86,16 +96,17 @@ extern BOOL CGPointIsNull(CGPoint point) [flattenedSublayouts addObject:[ASLayout layoutWithLayoutableObject:context.layout.layoutableObject size:context.layout.size position:context.absolutePosition - sublayouts:nil]]; + sublayouts:nil + flattened:context.layout.flattened]]; } for (ASLayout *sublayout in context.layout.sublayouts) { - queue.push({sublayout, context.absolutePosition + sublayout.position, NO}); + queue.push({sublayout, context.absolutePosition + sublayout.position, NO, context.layout.flattened}); } } } - return [ASLayout layoutWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts]; + return [ASLayout flattenedLayoutWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts]; } @end From 043012718b167fc57523722c4fba448a4c0ebd64 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 8 Feb 2016 15:54:21 -0800 Subject: [PATCH 090/224] Remove debug log --- AsyncDisplayKit/ASDisplayNode.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 302e7ed9c3..88b249bf96 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2228,7 +2228,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } subnodeFrame.size = adjustedSize; - NSLog(@"Adjusted frame: %@", NSStringFromCGRect(subnodeFrame)); return subnodeFrame; } From 8238da8d001ca95fb6e4d137aad07f94a2182446 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Mon, 8 Feb 2016 17:19:41 -0800 Subject: [PATCH 091/224] Fix node deletion and flatten identification issues --- AsyncDisplayKit/ASDisplayNode.mm | 64 ++++++++----------- AsyncDisplayKit/Layout/ASLayout.h | 20 +++++- AsyncDisplayKit/Layout/ASLayout.mm | 18 ++++-- .../ASDisplayNodeImplicitHierarchyTests.m | 5 +- 4 files changed, 59 insertions(+), 48 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 88b249bf96..e84db32824 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -60,6 +60,11 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS return self; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ %p node = %@, index = %ld>", self.class, self, _node, (long)_index]; +} + @end @interface ASDisplayNode () @@ -655,14 +660,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if ([[self class] usesImplicitHierarchyManagement]) { if (_layout) { NSIndexSet *insertions, *deletions; - // TODO: Filter the flattened layouts, since it's including nodes that are not in the hierarchy - [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + [_layout.immediateSublayouts asdk_diffWithArray:newLayout.immediateSublayouts + insertions:&insertions + deletions:&deletions + compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; - _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions offsetIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.immediateSublayouts count])]; _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; _deletedSubnodes = nil; } @@ -702,38 +709,31 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) /** @abstract Retrieves nodes at the given indexes from the layout's sublayouts */ -- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes +- (NSMutableArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes { - NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; - NSInteger idx = [indexes firstIndex]; - while (idx != NSNotFound) { - ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; - ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); - [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; - idx = [indexes indexGreaterThanIndex:idx]; - } - return result; + return [self _nodesInLayout:layout atIndexes:indexes filterNodes:nil]; } /** - @abstract Retrieves nodes at the given indexes from the layout's sublayouts, shifting the target index such that insertions can happen before deletions + @abstract Retrieves nodes at the given indexes from the layout's sublayouts, skipping nodes that are in the `filterNodes` list */ -- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes offsetIndexes:(NSIndexSet *)offsets +- (NSMutableArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes filterNodes:(NSArray<_ASDisplayNodePosition *> *)filterNodes { NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; - NSInteger offset = 0; - NSInteger offsetIndex = -1; NSInteger idx = [indexes firstIndex]; while (idx != NSNotFound) { - ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; + BOOL skip = NO; + ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); - // Offset deletions such that insertions can be performed first - NSInteger j = [offsets indexLessThanOrEqualToIndex:offsetIndex]; - if (j != NSNotFound && offsetIndex < j) { - offset++; - offsetIndex = j; + for (_ASDisplayNodePosition *filter in filterNodes) { + if (node == filter.node) { + skip = YES; + break; + } + } + if (!skip) { + [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; } - [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx + offset]]; idx = [indexes indexGreaterThanIndex:idx]; } return result; @@ -1071,7 +1071,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { if ([_insertedSubnodes count]) { for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + [self insertSubnode:position.node atIndex:position.index]; } _insertedSubnodes = nil; } @@ -1081,22 +1081,12 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo { if ([_deletedSubnodes count]) { for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; + [position.node removeFromSupernode]; } _deletedSubnodes = nil; } } -- (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx -{ - [self insertSubnode:node atIndex:idx]; -} - -- (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx -{ - [node removeFromSupernode]; -} - #pragma mark - _ASTransitionContextDelegate - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 23545cebf2..75fd51e264 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -21,23 +21,37 @@ extern BOOL CGPointIsNull(CGPoint point); /** Represents a computed immutable layout tree. */ @interface ASLayout : NSObject +/** + * The underlying object described by this layout + */ @property (nonatomic, weak, readonly) id layoutableObject; + +/** + * Size of the current layout + */ @property (nonatomic, readonly) CGSize size; + /** * Position in parent. Default to CGPointNull. * * @discussion When being used as a sublayout, this property must not equal CGPointNull. */ @property (nonatomic, readwrite) CGPoint position; -/** + +/** * Array of ASLayouts. Each must have a valid non-null position. */ @property (nonatomic, readonly) NSArray *sublayouts; /** - Whether the current layout has been flattened + * A list of sublayouts that were not already flattened. */ -@property (nonatomic, readonly) BOOL flattened; +@property (nonatomic, readonly) NSArray *immediateSublayouts; + +/** + * A boolean describing if the current layout has been flattened. + */ +@property (nonatomic, readonly, getter=isFlattened) BOOL flattened; /** * Initializer. diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 02ae752588..c2873b4f00 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -47,6 +47,14 @@ extern BOOL CGPointIsNull(CGPoint point) } l->_sublayouts = [sublayouts copy]; l->_flattened = flattened; + + NSMutableArray *result = [NSMutableArray array]; + for (ASLayout *sublayout in l->_sublayouts) { + if (!sublayout.isFlattened) { + [result addObject:sublayout]; + } + } + l->_immediateSublayouts = result; } return l; } @@ -81,9 +89,9 @@ extern BOOL CGPointIsNull(CGPoint point) BOOL flattened; }; - // Stack of Contexts, used to keep track of sublayouts while traversing this layout in a BFS fashion. + // Queue used to keep track of sublayouts while traversing this layout in a BFS fashion. std::queue queue; - queue.push({self, CGPointMake(0, 0), NO}); + queue.push({self, CGPointMake(0, 0), NO, NO}); while (!queue.empty()) { Context &context = queue.front(); @@ -97,11 +105,13 @@ extern BOOL CGPointIsNull(CGPoint point) size:context.layout.size position:context.absolutePosition sublayouts:nil - flattened:context.layout.flattened]]; + flattened:context.flattened]]; } for (ASLayout *sublayout in context.layout.sublayouts) { - queue.push({sublayout, context.absolutePosition + sublayout.position, NO, context.layout.flattened}); + // Mark layout trees that have already been flattened for future identification of immediate sublayouts + BOOL flattened = context.flattened ?: context.layout.flattened; + queue.push({sublayout, context.absolutePosition + sublayout.position, NO, flattened}); } } } diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index c565297d7c..0bb9fa327d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -86,7 +86,6 @@ return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[stack1, stack2, node5]]; }; [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; - [node layout]; // Layout immediately XCTAssertEqual(node.subnodes[0], node5); XCTAssertEqual(node.subnodes[1], node1); XCTAssertEqual(node.subnodes[2], node2); @@ -112,14 +111,12 @@ }; [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; - [node layout]; // Layout immediately XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); node.layoutState = @2; - [node invalidateCalculatedLayout]; // TODO(levi): Look into a way where measureWithSizeRange resizes when a new hierarchy is introduced but the size has not changed + [node invalidateCalculatedLayout]; [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; - [node layout]; // Layout immediately XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node3); From d8d76635ff3b525a80e3e84a44b09d635a5dcf34 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 11:05:06 -0800 Subject: [PATCH 092/224] Perform animation transition on `measureWithSizeRange` instead of separate method --- AsyncDisplayKit/ASContextTransitioning.h | 4 ++ AsyncDisplayKit/ASDisplayNode+Beta.h | 30 ++++++++++ AsyncDisplayKit/ASDisplayNode+Subclasses.h | 14 ----- AsyncDisplayKit/ASDisplayNode.h | 12 ---- AsyncDisplayKit/ASDisplayNode.mm | 66 +++++++++++++--------- AsyncDisplayKit/_ASTransitionContext.h | 6 +- AsyncDisplayKit/_ASTransitionContext.m | 4 +- 7 files changed, 80 insertions(+), 56 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 37f2ae6b98..8f0493a3b2 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -15,6 +15,10 @@ */ - (BOOL)isAnimated; +- (ASLayout *)layout; + +- (ASSizeRange)constrainedSize; + /** @abstract The frame for the given node before the transition began. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 7cd124a7ae..fff3fa3463 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -6,6 +6,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "ASContextTransitioning.h" + @interface ASDisplayNode (Beta) + (BOOL)usesImplicitHierarchyManagement; @@ -37,4 +39,32 @@ */ @property (nonatomic, strong) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext; +/** @name Layout Transitioning */ + +/** + @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. + */ +- (void)animateLayoutTransition:(id)context; + +/** + @discussion A place to clean up your nodes after the transition + */ +- (void)didCompleteLayoutTransition:(id)context; + +/** + @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + + @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + */ +//- (void)transitionLayoutWithAnimation:(BOOL)animated; + +/** + @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + + @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + */ +//- (void)transitionLayoutThatFits:(ASSizeRange)constrainedSize animated:(BOOL)animated; + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; + @end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index e571503b81..699b6213ed 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -12,7 +12,6 @@ #import #import #import -#import @class ASLayoutSpec; @@ -156,19 +155,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)invalidateCalculatedLayout; -/** @name Layout Transitioning */ - -/** - @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. - */ -- (void)animateLayoutTransition:(id)context; - -/** - @discussion A place to clean up your nodes after the transition - */ -- (void)didCompleteLayoutTransition:(id)context; - - /** @name Drawing */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 773ac7d7bf..2f67dc170c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -571,18 +571,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASDisplayNode (Transitioning) - -/** - @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. - - @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. - */ -- (void)transitionLayoutWithAnimation:(BOOL)animated; - -@end - - /** * Convenience methods for debugging. */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e84db32824..d87fc4656b 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -644,6 +644,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + return [self measureWithSizeRange:constrainedSize animated:NO]; +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); @@ -651,11 +656,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (![self __shouldSize]) return nil; + ASLayout *newLayout; + // only calculate the size if // - we haven't already // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; + newLayout = [self calculateLayoutThatFits:constrainedSize]; if ([[self class] usesImplicitHierarchyManagement]) { if (_layout) { @@ -674,22 +681,37 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _deletedSubnodes = nil; } - if (!_deferImmediateHierarchyManagement) { + if (animated) { + [self __transitionToLayout:newLayout constrainedSize:constrainedSize animated:animated]; + } else { [self __implicitlyInsertSubnodes]; [self __implicitlyRemoveSubnodes]; + [self __updateLayout:newLayout constrainedSize:constrainedSize]; } + } else { + // 1.9.x code path + [self __updateLayout:newLayout constrainedSize:constrainedSize]; } - - _layout = newLayout; - _constrainedSize = constrainedSize; - _flags.isMeasured = YES; - [self calculatedLayoutDidChange]; } + return newLayout; +} - ASDisplayNodeAssertTrue(_layout.layoutableObject == self); - ASDisplayNodeAssertTrue(_layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(_layout.size.height >= 0.0); +- (void)__updateLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize +{ + ASDisplayNodeAssertTrue(layout.layoutableObject == self); + ASDisplayNodeAssertTrue(layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(layout.size.height >= 0.0); + _layout = layout; + _constrainedSize = constrainedSize; + _flags.isMeasured = YES; + [self calculatedLayoutDidChange]; + + [self __primePlaceholder]; +} + +- (void)__primePlaceholder +{ // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed // to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder if (self.placeholderEnabled && [self _displaysAsynchronously] && @@ -702,8 +724,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self _setupPlaceholderLayerContents]; } } - - return _layout; } /** @@ -1033,23 +1053,12 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo #pragma mark - Layout Transition -- (void)transitionLayoutWithAnimation:(BOOL)animated +- (void)__transitionToLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated { - [self transitionLayoutThatFits:_constrainedSize animated:animated]; -} - -- (void)transitionLayoutThatFits:(ASSizeRange)constrainedSize animated:(BOOL)animated -{ - [self invalidateCalculatedLayout]; - _deferImmediateHierarchyManagement = YES; - [self measureWithSizeRange:constrainedSize]; // Generate a new layout - _deferImmediateHierarchyManagement = NO; - [self __transitionLayoutWithAnimation:animated]; -} - -- (void)__transitionLayoutWithAnimation:(BOOL)animated -{ - _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; + _transitionContext = [[_ASTransitionContext alloc] initWithLayout:layout + constrainedSize:constrainedSize + animated:animated + delegate:self]; [self __implicitlyInsertSubnodes]; [self animateLayoutTransition:_transitionContext]; } @@ -1063,6 +1072,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (void)didCompleteTransitionLayout:(id)context { [self __implicitlyRemoveSubnodes]; + [self __updateLayout:context.layout constrainedSize:context.constrainedSize]; } #pragma mark - Implicit node hierarchy managagment diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index ab396514cb..21d6babea7 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -25,6 +25,10 @@ @property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated; -- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; +@property (strong, readonly) ASLayout *layout; + +@property (assign, readonly) ASSizeRange constrainedSize; + +- (instancetype)initWithLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; @end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index 059138e6ae..d5dd171ba4 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -16,10 +16,12 @@ @implementation _ASTransitionContext -- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate +- (instancetype)initWithLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate { self = [super init]; if (self) { + _layout = layout; + _constrainedSize = constrainedSize; _animated = animated; _delegate = delegate; } From a93013702d3cf7ae957eefaddbdd200059087350 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 13:10:37 -0800 Subject: [PATCH 093/224] Add transition context to iOS framework --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 34da2492a9..44de2f1798 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -465,6 +465,7 @@ DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; @@ -1470,6 +1471,7 @@ B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, + DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, 254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, From 8cdea808ec580f72f93c7c732fd78249baf1c593 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 13:11:17 -0800 Subject: [PATCH 094/224] Expose calculated frame on layout --- AsyncDisplayKit/Layout/ASLayout.h | 5 +++++ AsyncDisplayKit/Layout/ASLayout.mm | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 75fd51e264..3dffdf227a 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -122,6 +122,11 @@ extern BOOL CGPointIsNull(CGPoint point); */ - (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *evaluatedLayout))predicateBlock; +/** + * @abstract Returns a valid frame for the current layout computed with the size and position. + */ +- (CGRect)frame; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index c2873b4f00..0e15fcfc16 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -119,4 +119,32 @@ extern BOOL CGPointIsNull(CGPoint point) return [ASLayout flattenedLayoutWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts]; } +- (CGRect)frame +{ + CGRect subnodeFrame = CGRectZero; + CGPoint adjustedOrigin = _position; + if (isfinite(adjustedOrigin.x) == NO) { + ASDisplayNodeAssert(0, @"Layout has an invalid position"); + adjustedOrigin.x = 0; + } + if (isfinite(adjustedOrigin.y) == NO) { + ASDisplayNodeAssert(0, @"Layout has an invalid position"); + adjustedOrigin.y = 0; + } + subnodeFrame.origin = adjustedOrigin; + + CGSize adjustedSize = _size; + if (isfinite(adjustedSize.width) == NO) { + ASDisplayNodeAssert(0, @"Layout has an invalid size"); + adjustedSize.width = 0; + } + if (isfinite(adjustedSize.height) == NO) { + ASDisplayNodeAssert(0, @"Layout has an invalid position"); + adjustedSize.height = 0; + } + subnodeFrame.size = adjustedSize; + + return subnodeFrame; +} + @end From 4361c3bbb417f6e70480f36db566d53eb72fbe92 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 13:11:43 -0800 Subject: [PATCH 095/224] Remove disabled APIs --- AsyncDisplayKit/ASDisplayNode+Beta.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index fff3fa3463..abc95d3bd7 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -56,15 +56,6 @@ @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. */ -//- (void)transitionLayoutWithAnimation:(BOOL)animated; - -/** - @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. - - @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. - */ -//- (void)transitionLayoutThatFits:(ASSizeRange)constrainedSize animated:(BOOL)animated; - - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; @end From 1513ee8ca5e646fc8ea36c1af289c307bf0f6147 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 14:12:05 -0800 Subject: [PATCH 096/224] Expose the layout's immediate sublayouts as the accessing nodes --- AsyncDisplayKit/ASContextTransitioning.h | 11 +++ AsyncDisplayKit/ASDisplayNode.mm | 89 +++++++++--------------- AsyncDisplayKit/_ASTransitionContext.h | 3 +- AsyncDisplayKit/_ASTransitionContext.m | 25 ++++++- 4 files changed, 68 insertions(+), 60 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 8f0493a3b2..3b48a5e02b 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -15,10 +15,21 @@ */ - (BOOL)isAnimated; +/** + * @abstract The destination layout being transitioned to + */ - (ASLayout *)layout; +/** + * @abstrat The destination constrainedSize being transitioned to + */ - (ASSizeRange)constrainedSize; +/** + * @abstract Subnodes in the new layout + */ +- (NSArray *)subnodes; + /** @abstract The frame for the given node before the transition began. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index d87fc4656b..2000f87de0 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -652,11 +652,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); + ASLayout *newLayout; if (![self __shouldSize]) - return nil; - - ASLayout *newLayout; + return newLayout; // only calculate the size if // - we haven't already @@ -664,32 +663,29 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { newLayout = [self calculateLayoutThatFits:constrainedSize]; - if ([[self class] usesImplicitHierarchyManagement]) { - if (_layout) { - NSIndexSet *insertions, *deletions; - [_layout.immediateSublayouts asdk_diffWithArray:newLayout.immediateSublayouts - insertions:&insertions - deletions:&deletions - compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { - return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); - }]; - _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; - _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; - } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.immediateSublayouts count])]; - _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; - _deletedSubnodes = nil; - } - - if (animated) { - [self __transitionToLayout:newLayout constrainedSize:constrainedSize animated:animated]; - } else { + if (_layout) { + NSIndexSet *insertions, *deletions; + [_layout.immediateSublayouts asdk_diffWithArray:newLayout.immediateSublayouts + insertions:&insertions + deletions:&deletions + compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); + }]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; + } else { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.immediateSublayouts count])]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; + _deletedSubnodes = nil; + } + + if (animated) { + [self __transitionToLayout:newLayout constrainedSize:constrainedSize animated:animated]; + } else { + if ([[self class] usesImplicitHierarchyManagement]) { [self __implicitlyInsertSubnodes]; [self __implicitlyRemoveSubnodes]; - [self __updateLayout:newLayout constrainedSize:constrainedSize]; } - } else { - // 1.9.x code path [self __updateLayout:newLayout constrainedSize:constrainedSize]; } } @@ -1079,52 +1075,33 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (void)__implicitlyInsertSubnodes { - if ([_insertedSubnodes count]) { - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self insertSubnode:position.node atIndex:position.index]; - } - _insertedSubnodes = nil; + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self insertSubnode:position.node atIndex:position.index]; } + _insertedSubnodes = nil; } - (void)__implicitlyRemoveSubnodes { - if ([_deletedSubnodes count]) { - for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [position.node removeFromSupernode]; - } - _deletedSubnodes = nil; + for (_ASDisplayNodePosition *position in _deletedSubnodes) { + [position.node removeFromSupernode]; } + _deletedSubnodes = nil; } #pragma mark - _ASTransitionContextDelegate +- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + return _subnodes; +} + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { [self didCompleteTransitionLayout:context]; _transitionContext = nil; } -- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node -{ - for (ASDisplayNode *subnode in _subnodes) { - if (ASObjectIsEqual(node, subnode)) { - return node.frame; - } - } - return CGRectNull; -} - -- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node -{ - for (ASLayout *layout in _layout.sublayouts) { - if (ASObjectIsEqual(node, layout.layoutableObject)) { - return [self _adjustedFrameForLayout:layout]; - } - } - return CGRectNull; -} - #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index 21d6babea7..a35a395c0b 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -15,9 +15,8 @@ @protocol _ASTransitionContextDelegate +- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context; - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; -- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; -- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; @end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index d5dd171ba4..5a6d1e08d4 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -8,6 +8,8 @@ #import "_ASTransitionContext.h" +#import "ASLayout.h" + @interface _ASTransitionContext () @property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate; @@ -30,12 +32,31 @@ - (CGRect)initialFrameForNode:(ASDisplayNode *)node { - return [_delegate transitionContext:self initialFrameForNode:node]; + for (ASDisplayNode *subnode in [_delegate currentSubnodesWithTransitionContext:self]) { + if (node == subnode) { + return node.frame; + } + } + return CGRectZero; } - (CGRect)finalFrameForNode:(ASDisplayNode *)node { - return [_delegate transitionContext:self finalFrameForNode:node]; + for (ASLayout *layout in _layout.immediateSublayouts) { + if (layout.layoutableObject == node) { + return [layout frame]; + } + } + return CGRectZero; +} + +- (NSArray *)subnodes +{ + NSMutableArray *subnodes = [NSMutableArray array]; + for (ASLayout *sublayout in _layout.immediateSublayouts) { + [subnodes addObject:(ASDisplayNode *)sublayout.layoutableObject]; + } + return subnodes; } - (void)completeTransition:(BOOL)didComplete From 5cf5cb84522d2b42089cc80dac3920b5ac7d6a97 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 15:32:55 -0800 Subject: [PATCH 097/224] Delegate layout frame calculation to sublayout method --- AsyncDisplayKit/ASDisplayNode.mm | 34 ++----------------- AsyncDisplayKit/ASViewController.m | 1 - AsyncDisplayKit/Details/_ASDisplayLayer.mm | 2 +- AsyncDisplayKit/Layout/ASLayout.h | 1 + .../Private/ASDisplayNodeInternal.h | 5 --- 5 files changed, 4 insertions(+), 39 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2000f87de0..fa8855110c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2164,11 +2164,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self __layoutSublayouts]; } else { // Assume that _layout was flattened and is 1-level deep. - CGRect subnodeFrame = CGRectZero; for (ASLayout *subnodeLayout in _layout.sublayouts) { ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); - subnodeFrame = [self _adjustedFrameForLayout:subnodeLayout]; - ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = subnodeFrame; + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; } } } @@ -2176,38 +2174,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)__layoutSublayouts { for (ASLayout *subnodeLayout in _layout.sublayouts) { - ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; } } -- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout -{ - CGRect subnodeFrame = CGRectZero; - CGPoint adjustedOrigin = layout.position; - if (isfinite(adjustedOrigin.x) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.x = 0; - } - if (isfinite(adjustedOrigin.y) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.y = 0; - } - subnodeFrame.origin = adjustedOrigin; - - CGSize adjustedSize = layout.size; - if (isfinite(adjustedSize.width) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); - adjustedSize.width = 0; - } - if (isfinite(adjustedSize.height) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedSize.height = 0; - } - subnodeFrame.size = adjustedSize; - - return subnodeFrame; -} - - (void)displayWillStart { // in case current node takes longer to display than it's subnodes, treat it as a dependent node diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index b626c7169c..78a2bac5e7 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -50,7 +50,6 @@ - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; - [_node measureWithSizeRange:[self nodeConstrainedSize]]; } - (void)viewDidLayoutSubviews diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 24ab6b0d0e..110336be97 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -85,7 +85,7 @@ } - (void)layoutSublayers -{ +{ [super layoutSublayers]; ASDisplayNode *node = self.asyncdisplaykit_node; diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 3dffdf227a..073e2a6a11 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -124,6 +124,7 @@ extern BOOL CGPointIsNull(CGPoint point); /** * @abstract Returns a valid frame for the current layout computed with the size and position. + * @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite. */ - (CGRect)frame; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 336069e62c..b96a8143fe 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -149,11 +149,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; -/** - Clamps the layout's origin or position to 0 if any of the calculated values are infinite. - */ -- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout; - // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. - (BOOL)__visibilityNotificationsDisabled; - (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; From 8bfa3e40128d9c67b2f26bba7230dd33dd2ce1aa Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 15:34:32 -0800 Subject: [PATCH 098/224] Remove ivar --- AsyncDisplayKit/Private/ASDisplayNodeInternal.h | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index b96a8143fe..23575f1398 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -66,7 +66,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSMutableArray *_subnodes; _ASTransitionContext *_transitionContext; - BOOL _deferImmediateHierarchyManagement; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; From 8737e242f8cabd625c49c9a3872e5e46e41e5bdd Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 16:11:42 -0800 Subject: [PATCH 099/224] Extract measurement and transition into different methods based on feedback --- AsyncDisplayKit/ASDisplayNode+Beta.h | 14 +- AsyncDisplayKit/ASDisplayNode.mm | 199 +++++++++--------- .../Private/ASDisplayNodeInternal.h | 4 + 3 files changed, 115 insertions(+), 102 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index abc95d3bd7..b4c2042a24 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -34,28 +34,26 @@ /** * @abstract allow modification of a context after the node's content is drawn - * - * @discussion */ @property (nonatomic, strong) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext; /** @name Layout Transitioning */ /** - @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. + * @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. */ - (void)animateLayoutTransition:(id)context; /** - @discussion A place to clean up your nodes after the transition + * @discussion A place to clean up your nodes after the transition */ - (void)didCompleteLayoutTransition:(id)context; /** - @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. - - @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + * @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + * + * @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. */ -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; +- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fa8855110c..163ca6adeb 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -636,7 +636,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _flags.layerBacked; } -#pragma mark - +#pragma mark - Layout measurement calculation - (CGSize)measure:(CGSize)constrainedSize { @@ -645,61 +645,80 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - return [self measureWithSizeRange:constrainedSize animated:NO]; + return [self measureWithSizeRange:constrainedSize completion:^{ + if ([[self class] usesImplicitHierarchyManagement]) { + [self __implicitlyInsertSubnodes]; + [self __implicitlyRemoveSubnodes]; + } + [self __applyPendingLayout]; + }]; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); - ASLayout *newLayout; - if (![self __shouldSize]) - return newLayout; + return nil; // only calculate the size if // - we haven't already // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - newLayout = [self calculateLayoutThatFits:constrainedSize]; + _pendingLayout = [self calculateLayoutThatFits:constrainedSize]; + _pendingConstrainedSize = constrainedSize; + [self __calculateSubnodeOperations]; - if (_layout) { - NSIndexSet *insertions, *deletions; - [_layout.immediateSublayouts asdk_diffWithArray:newLayout.immediateSublayouts - insertions:&insertions - deletions:&deletions - compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { - return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); - }]; - _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; - _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; - } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.immediateSublayouts count])]; - _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; - _deletedSubnodes = nil; - } - - if (animated) { - [self __transitionToLayout:newLayout constrainedSize:constrainedSize animated:animated]; - } else { - if ([[self class] usesImplicitHierarchyManagement]) { - [self __implicitlyInsertSubnodes]; - [self __implicitlyRemoveSubnodes]; - } - [self __updateLayout:newLayout constrainedSize:constrainedSize]; - } + completion(); } - return newLayout; + + return _pendingLayout; } -- (void)__updateLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize +- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated { - ASDisplayNodeAssertTrue(layout.layoutableObject == self); - ASDisplayNodeAssertTrue(layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(layout.size.height >= 0.0); + [self invalidateCalculatedLayout]; + return [self measureWithSizeRange:constrainedSize completion:^{ + _transitionContext = [[_ASTransitionContext alloc] initWithLayout:_pendingLayout + constrainedSize:constrainedSize + animated:animated + delegate:self]; + [self __implicitlyInsertSubnodes]; + [self animateLayoutTransition:_transitionContext]; + }]; +} + +- (void)__calculateSubnodeOperations +{ + if (_layout) { + NSIndexSet *insertions, *deletions; + [_layout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts + insertions:&insertions + deletions:&deletions + compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { + return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); + }]; + _insertedSubnodes = [self _nodesInLayout:_pendingLayout atIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; + } else { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])]; + _insertedSubnodes = [self _nodesInLayout:_pendingLayout atIndexes:indexes]; + _deletedSubnodes = nil; + } +} + +- (void)__applyPendingLayout +{ + ASDisplayNodeAssertTrue(_pendingLayout.layoutableObject == self); + ASDisplayNodeAssertTrue(_pendingLayout.size.width >= 0.0); + ASDisplayNodeAssertTrue(_pendingLayout.size.height >= 0.0); + + _layout = _pendingLayout; + _pendingLayout = nil; + + _constrainedSize = _pendingConstrainedSize; + _pendingConstrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero); - _layout = layout; - _constrainedSize = constrainedSize; _flags.isMeasured = YES; [self calculatedLayoutDidChange]; @@ -760,6 +779,53 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // subclass override } +#pragma mark - Layout Transition + +- (void)animateLayoutTransition:(id)context +{ + [self __layoutSublayouts]; + [context completeTransition:YES]; +} + +- (void)didCompleteTransitionLayout:(id)context +{ + [self __implicitlyRemoveSubnodes]; + [self __applyPendingLayout]; +} + +#pragma mark - Implicit node hierarchy managagment + +- (void)__implicitlyInsertSubnodes +{ + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self insertSubnode:position.node atIndex:position.index]; + } + _insertedSubnodes = nil; +} + +- (void)__implicitlyRemoveSubnodes +{ + for (_ASDisplayNodePosition *position in _deletedSubnodes) { + [position.node removeFromSupernode]; + } + _deletedSubnodes = nil; +} + +#pragma mark - _ASTransitionContextDelegate + +- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + return _subnodes; +} + +- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete +{ + [self didCompleteTransitionLayout:context]; + _transitionContext = nil; +} + +#pragma mark - Asynchronous display + - (BOOL)displaysAsynchronously { ASDN::MutexLocker l(_propertyLock); @@ -1047,61 +1113,6 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return CGRectApplyAffineTransform(rect, flattenedTransform); } -#pragma mark - Layout Transition - -- (void)__transitionToLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated -{ - _transitionContext = [[_ASTransitionContext alloc] initWithLayout:layout - constrainedSize:constrainedSize - animated:animated - delegate:self]; - [self __implicitlyInsertSubnodes]; - [self animateLayoutTransition:_transitionContext]; -} - -- (void)animateLayoutTransition:(id)context -{ - [self __layoutSublayouts]; - [context completeTransition:YES]; -} - -- (void)didCompleteTransitionLayout:(id)context -{ - [self __implicitlyRemoveSubnodes]; - [self __updateLayout:context.layout constrainedSize:context.constrainedSize]; -} - -#pragma mark - Implicit node hierarchy managagment - -- (void)__implicitlyInsertSubnodes -{ - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self insertSubnode:position.node atIndex:position.index]; - } - _insertedSubnodes = nil; -} - -- (void)__implicitlyRemoveSubnodes -{ - for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [position.node removeFromSupernode]; - } - _deletedSubnodes = nil; -} - -#pragma mark - _ASTransitionContextDelegate - -- (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context -{ - return _subnodes; -} - -- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete -{ - [self didCompleteTransitionLayout:context]; - _transitionContext = nil; -} - #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 23575f1398..d1dc25b2c5 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -61,7 +61,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo CGFloat _contentsScaleForDisplay; ASLayout *_layout; + ASLayout *_pendingLayout; + ASSizeRange _constrainedSize; + ASSizeRange _pendingConstrainedSize; + UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; From 3444fa18dde3fe2eb8881cdb1d5b6b2cc410e1f2 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 16:13:48 -0800 Subject: [PATCH 100/224] Remove viewWillLayoutSubviews from ASViewController --- AsyncDisplayKit/ASViewController.m | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 78a2bac5e7..63d9f3e566 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -47,11 +47,6 @@ self.view = _node.view; } -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; -} - - (void)viewDidLayoutSubviews { if (_ensureDisplayed && self.neverShowPlaceholders) { From a00e9bb41ce1aeaa11d1054912e36dafe79c58f7 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 16:24:24 -0800 Subject: [PATCH 101/224] Clean up node transition API --- AsyncDisplayKit/ASDisplayNode+Beta.h | 12 ++++++++++-- AsyncDisplayKit/ASDisplayNode.mm | 7 ++++++- AsyncDisplayKit/ASViewController.m | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index b4c2042a24..2642ba3f50 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -50,10 +50,18 @@ - (void)didCompleteLayoutTransition:(id)context; /** - * @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + * @abstract Transitions the current layout with a new constrained size. * - * @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + * @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. */ - (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated; +/** + * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. + * + * @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + */ +- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated; + @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 163ca6adeb..ae39983c8e 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -675,9 +675,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _pendingLayout; } -- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated +- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated { [self invalidateCalculatedLayout]; + [self transitionLayoutWithSizeRange:_constrainedSize animated:animated]; +} + +- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated +{ return [self measureWithSizeRange:constrainedSize completion:^{ _transitionContext = [[_ASTransitionContext alloc] initWithLayout:_pendingLayout constrainedSize:constrainedSize diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 63d9f3e566..4e93b70343 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -59,6 +59,9 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + [_node measureWithSizeRange:[self nodeConstrainedSize]]; + _ensureDisplayed = YES; [_node recursivelyFetchData]; } From e57761ffbefa921e5877ce097ffa24fe15cd9429 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 9 Feb 2016 17:45:52 -0800 Subject: [PATCH 102/224] Fix pending layout reference causing nil references --- AsyncDisplayKit/ASDisplayNode.mm | 64 +++++++++---------- .../Private/ASDisplayNodeInternal.h | 4 -- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ae39983c8e..cdcd1b54aa 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -645,46 +645,46 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - return [self measureWithSizeRange:constrainedSize completion:^{ + return [self measureWithSizeRange:constrainedSize completion:^(ASLayout *pendingLayout, ASSizeRange constrainedSize) { if ([[self class] usesImplicitHierarchyManagement]) { [self __implicitlyInsertSubnodes]; [self __implicitlyRemoveSubnodes]; } - [self __applyPendingLayout]; + [self __applyLayout:pendingLayout constrainedSize:constrainedSize]; }]; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)(ASLayout *, ASSizeRange))completion { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); if (![self __shouldSize]) return nil; + ASLayout *pendingLayout; + // only calculate the size if // - we haven't already // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - _pendingLayout = [self calculateLayoutThatFits:constrainedSize]; - _pendingConstrainedSize = constrainedSize; - [self __calculateSubnodeOperations]; - - completion(); + pendingLayout = [self calculateLayoutThatFits:constrainedSize]; + [self __calculateSubnodeOperationsWithPendingLayout:pendingLayout currentLayout:_layout]; + completion(pendingLayout, constrainedSize); } - return _pendingLayout; + return pendingLayout; } - (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated { [self invalidateCalculatedLayout]; - [self transitionLayoutWithSizeRange:_constrainedSize animated:animated]; + return [self transitionLayoutWithSizeRange:_constrainedSize animated:animated]; } - (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated { - return [self measureWithSizeRange:constrainedSize completion:^{ - _transitionContext = [[_ASTransitionContext alloc] initWithLayout:_pendingLayout + return [self measureWithSizeRange:constrainedSize completion:^(ASLayout *pendingLayout, ASSizeRange constrainedSize) { + _transitionContext = [[_ASTransitionContext alloc] initWithLayout:pendingLayout constrainedSize:constrainedSize animated:animated delegate:self]; @@ -693,47 +693,43 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) }]; } -- (void)__calculateSubnodeOperations +- (void)__calculateSubnodeOperationsWithPendingLayout:(ASLayout *)pendingLayout currentLayout:(ASLayout *)currentLayout { if (_layout) { NSIndexSet *insertions, *deletions; - [_layout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts + [currentLayout.immediateSublayouts asdk_diffWithArray:pendingLayout.immediateSublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - _insertedSubnodes = [self _nodesInLayout:_pendingLayout atIndexes:insertions]; - _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions filterNodes:_insertedSubnodes]; + _insertedSubnodes = [self _nodesInLayout:pendingLayout atIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:currentLayout atIndexes:deletions filterNodes:_insertedSubnodes]; } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])]; - _insertedSubnodes = [self _nodesInLayout:_pendingLayout atIndexes:indexes]; + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.immediateSublayouts count])]; + _insertedSubnodes = [self _nodesInLayout:pendingLayout atIndexes:indexes]; _deletedSubnodes = nil; } } -- (void)__applyPendingLayout +- (void)__applyLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize { - ASDisplayNodeAssertTrue(_pendingLayout.layoutableObject == self); - ASDisplayNodeAssertTrue(_pendingLayout.size.width >= 0.0); - ASDisplayNodeAssertTrue(_pendingLayout.size.height >= 0.0); - - _layout = _pendingLayout; - _pendingLayout = nil; - - _constrainedSize = _pendingConstrainedSize; - _pendingConstrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero); + ASDisplayNodeAssertTrue(layout.layoutableObject == self); + ASDisplayNodeAssertTrue(layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(layout.size.height >= 0.0); + _layout = layout; + _constrainedSize = constrainedSize; _flags.isMeasured = YES; [self calculatedLayoutDidChange]; - [self __primePlaceholder]; -} - -- (void)__primePlaceholder -{ // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed // to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder + [self __initPlaceholder]; +} + +- (void)__initPlaceholder +{ if (self.placeholderEnabled && [self _displaysAsynchronously] && _layout.size.width > 0.0 && _layout.size.height > 0.0) { if (!_placeholderImage) { @@ -795,7 +791,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)didCompleteTransitionLayout:(id)context { [self __implicitlyRemoveSubnodes]; - [self __applyPendingLayout]; + [self __applyLayout:context.layout constrainedSize:context.constrainedSize]; } #pragma mark - Implicit node hierarchy managagment diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index d1dc25b2c5..23575f1398 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -61,11 +61,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo CGFloat _contentsScaleForDisplay; ASLayout *_layout; - ASLayout *_pendingLayout; - ASSizeRange _constrainedSize; - ASSizeRange _pendingConstrainedSize; - UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; From d669cadcba47967bde5d5019ddf4536b7f7e4884 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 10:01:38 -0800 Subject: [PATCH 103/224] Use core layoutSublayouts method for layout step --- AsyncDisplayKit/ASDisplayNode.mm | 10 +--------- AsyncDisplayKit/_ASTransitionContext.m | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index cdcd1b54aa..c6798adbad 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2172,15 +2172,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return; } - if ([[self class] usesImplicitHierarchyManagement]) { - [self __layoutSublayouts]; - } else { - // Assume that _layout was flattened and is 1-level deep. - for (ASLayout *subnodeLayout in _layout.sublayouts) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); - ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; - } - } + [self __layoutSublayouts]; } - (void)__layoutSublayouts diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index 5a6d1e08d4..bb599f894a 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -42,7 +42,7 @@ - (CGRect)finalFrameForNode:(ASDisplayNode *)node { - for (ASLayout *layout in _layout.immediateSublayouts) { + for (ASLayout *layout in _layout.sublayouts) { if (layout.layoutableObject == node) { return [layout frame]; } From 499c3331ce43013d7025081b95ecb7c2c377541f Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 11:08:30 -0800 Subject: [PATCH 104/224] Optimize insertion/deletion node calculation & expose inserted/deleted subnodes to transition context --- AsyncDisplayKit/ASContextTransitioning.h | 10 ++ AsyncDisplayKit/ASDisplayNode.mm | 107 +++++++++--------- .../Private/ASDisplayNodeInternal.h | 9 +- AsyncDisplayKit/_ASTransitionContext.h | 3 + AsyncDisplayKit/_ASTransitionContext.m | 12 ++ 5 files changed, 83 insertions(+), 58 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 3b48a5e02b..19ea88861c 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -30,6 +30,16 @@ */ - (NSArray *)subnodes; +/** + * @abstract Subnodes that have been inserted in the layout transition + */ +- (NSArray *)insertedSubnodes; + +/** + * @abstract Subnodes that will be removed in the layout transition + */ +- (NSArray *)removedSubnodes; + /** @abstract The frame for the given node before the transition began. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c6798adbad..de276f83b5 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -32,41 +32,6 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; -@interface _ASDisplayNodePosition : NSObject - -@property (nonatomic, assign) NSUInteger index; -@property (nonatomic, strong) ASDisplayNode *node; - -+ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index; - -- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index; - -@end - -@implementation _ASDisplayNodePosition - -+ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index -{ - return [[self alloc] initWithNode:node atIndex:index]; -} - -- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index -{ - self = [super init]; - if (self) { - _node = node; - _index = index; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@ %p node = %@, index = %ld>", self.class, self, _node, (long)_index]; -} - -@end - @interface ASDisplayNode () /** @@ -703,12 +668,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - _insertedSubnodes = [self _nodesInLayout:pendingLayout atIndexes:insertions]; - _deletedSubnodes = [self _nodesInLayout:currentLayout atIndexes:deletions filterNodes:_insertedSubnodes]; + filterNodesInLayoutAtIndexes(pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); + filterNodesInLayoutAtIndexesWithIntersectingNodes(currentLayout, + deletions, + _insertedSubnodes, + &_removedSubnodes, + &_removedSubnodePositions); } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.immediateSublayouts count])]; - _insertedSubnodes = [self _nodesInLayout:pendingLayout atIndexes:indexes]; - _deletedSubnodes = nil; + filterNodesInLayoutAtIndexes(pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); + _removedSubnodes = nil; } } @@ -721,6 +690,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _layout = layout; _constrainedSize = constrainedSize; _flags.isMeasured = YES; + _insertedSubnodes = nil; + _removedSubnodes = nil; [self calculatedLayoutDidChange]; // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed @@ -743,36 +714,51 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } /** - @abstract Retrieves nodes at the given indexes from the layout's sublayouts + * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. */ -- (NSMutableArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes +static inline void filterNodesInLayoutAtIndexes( + ASLayout *layout, + NSIndexSet *indexes, + NSArray * __strong *storedNodes, + std::vector *storedPositions + ) { - return [self _nodesInLayout:layout atIndexes:indexes filterNodes:nil]; + filterNodesInLayoutAtIndexesWithIntersectingNodes(layout, indexes, nil, storedNodes, storedPositions); } /** - @abstract Retrieves nodes at the given indexes from the layout's sublayouts, skipping nodes that are in the `filterNodes` list + * @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector. + * @discussion If the node exists in the `intersectingNodes` array, the node is not added to `storedNodes`. */ -- (NSMutableArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes filterNodes:(NSArray<_ASDisplayNodePosition *> *)filterNodes +static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( + ASLayout *layout, + NSIndexSet *indexes, + NSArray *intersectingNodes, + NSArray * __strong *storedNodes, + std::vector *storedPositions + ) { - NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; + NSMutableArray *nodes = [NSMutableArray array]; + std::vector positions = std::vector(); NSInteger idx = [indexes firstIndex]; while (idx != NSNotFound) { BOOL skip = NO; ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; - ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); - for (_ASDisplayNodePosition *filter in filterNodes) { - if (node == filter.node) { + ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); + for (ASDisplayNode *i in intersectingNodes) { + if (node == i) { skip = YES; break; } } if (!skip) { - [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; + [nodes addObject:node]; + positions.push_back(idx); } idx = [indexes indexGreaterThanIndex:idx]; } - return result; + *storedNodes = nodes; + *storedPositions = positions; } - (void)calculatedLayoutDidChange @@ -798,18 +784,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)__implicitlyInsertSubnodes { - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self insertSubnode:position.node atIndex:position.index]; + for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) { + NSInteger p = _insertedSubnodePositions[i]; + [self insertSubnode:_insertedSubnodes[i] atIndex:p]; } - _insertedSubnodes = nil; } - (void)__implicitlyRemoveSubnodes { - for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [position.node removeFromSupernode]; + for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) { + [_removedSubnodes[i] removeFromSupernode]; } - _deletedSubnodes = nil; } #pragma mark - _ASTransitionContextDelegate @@ -819,6 +804,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _subnodes; } +- (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + return _insertedSubnodes; +} + +- (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context +{ + return _removedSubnodes; +} + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { [self didCompleteTransitionLayout:context]; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 23575f1398..378ec535b0 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -19,6 +19,8 @@ #import "ASLayoutOptions.h" #import "_ASTransitionContext.h" +#include + @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @@ -66,8 +68,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSMutableArray *_subnodes; _ASTransitionContext *_transitionContext; - NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; - NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; + + NSArray *_insertedSubnodes; + NSArray *_removedSubnodes; + std::vector _insertedSubnodePositions; + std::vector _removedSubnodePositions; ASDisplayNodeViewBlock _viewBlock; ASDisplayNodeLayerBlock _layerBlock; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index a35a395c0b..cf3ee5b10c 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -16,6 +16,9 @@ @protocol _ASTransitionContextDelegate - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context; +- (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context; +- (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context; + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; @end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index bb599f894a..753cac7b36 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -30,6 +30,8 @@ return self; } +#pragma mark - ASContextTransitioning Protocol Implementation + - (CGRect)initialFrameForNode:(ASDisplayNode *)node { for (ASDisplayNode *subnode in [_delegate currentSubnodesWithTransitionContext:self]) { @@ -59,6 +61,16 @@ return subnodes; } +- (NSArray *)insertedSubnodes +{ + return [_delegate insertedSubnodesWithTransitionContext:self]; +} + +- (NSArray *)removedSubnodes +{ + return [_delegate removedSubnodesWithTransitionContext:self]; +} + - (void)completeTransition:(BOOL)didComplete { [_delegate transitionContext:self didComplete:didComplete]; From 50a41df7b0c067454a497a10a0cb96d90f805637 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 11:42:41 -0800 Subject: [PATCH 105/224] Return current layout if pending layout isn't needed --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index de276f83b5..11b1d4130c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -626,7 +626,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (![self __shouldSize]) return nil; - ASLayout *pendingLayout; + ASLayout *pendingLayout = _layout; // only calculate the size if // - we haven't already From 870cc405e6bb30c383499bd8a7523023db9e2cd9 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 11:43:29 -0800 Subject: [PATCH 106/224] Remove removedSubnodes instead of insertedSubnodes --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 11b1d4130c..e5eaa46c33 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -792,7 +792,7 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (void)__implicitlyRemoveSubnodes { - for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) { + for (NSInteger i = 0; i < [_removedSubnodes count]; i++) { [_removedSubnodes[i] removeFromSupernode]; } } From 6aae68ead4148d074502222df27d17905e7050ce Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 11:59:34 -0800 Subject: [PATCH 107/224] Includ ASTransitionContext implementation in iOS framework --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 44de2f1798..081786c9ee 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -465,6 +465,7 @@ DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; @@ -1956,6 +1957,7 @@ 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, + DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, C78F7E2A1BF7808300CDEAFC /* ASTableNode.m in Sources */, From 622c851779d330edb76c98575c083e961556bee2 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Wed, 10 Feb 2016 12:24:45 -0800 Subject: [PATCH 108/224] Fix downloader test --- AsyncDisplayKit/Details/ASBasicImageDownloader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/AsyncDisplayKit/Details/ASBasicImageDownloader.h index 38b7b5a042..5d436e6d20 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.h +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Simple NSURLSession-based image downloader. */ -@interface ASBasicImageDownloader : NSObject +@interface ASBasicImageDownloader : NSObject + (instancetype)sharedImageDownloader; From e3315f4b449188ea5c6eb16d5d817c1fc098455d Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Wed, 10 Feb 2016 13:28:30 -0800 Subject: [PATCH 109/224] Skip decoding as ASDK will handle that. --- AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 49ab3f43e1..45f12cf004 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -49,7 +49,7 @@ downloadProgress:(void (^)(CGFloat progress))downloadProgressBlock completion:(void (^)(UIImage *image, NSError * error, id downloadIdentifier))completion { - return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL completion:^(PINRemoteImageManagerResult *result) { + return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult *result) { dispatch_async(callbackQueue, ^{ completion(result.image, result.error, result.UUID); }); From 6f37bb40d9a9e3a5a0b3586b293833c09ec2c6f7 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 14:39:46 -0800 Subject: [PATCH 110/224] Allow layouts to be accessible in context --- AsyncDisplayKit/ASContextTransitioning.h | 15 ++-- AsyncDisplayKit/ASDisplayNode.mm | 81 ++++++++++++------- .../Private/ASDisplayNodeInternal.h | 4 + AsyncDisplayKit/_ASTransitionContext.h | 10 +-- AsyncDisplayKit/_ASTransitionContext.m | 24 ++++-- 5 files changed, 87 insertions(+), 47 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 19ea88861c..694ab6bf83 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -8,6 +8,9 @@ #import +extern NSString * const ASTransitionContextFromLayoutKey; +extern NSString * const ASTransitionContextToLayoutKey; + @protocol ASContextTransitioning /** @@ -16,19 +19,19 @@ - (BOOL)isAnimated; /** - * @abstract The destination layout being transitioned to + * @abstract Retrieve either the "from" or "to" layout */ -- (ASLayout *)layout; +- (ASLayout *)layoutForKey:(NSString *)key; /** - * @abstrat The destination constrainedSize being transitioned to + * @abstract Retrieve either the "from" or "to" constrainedSize */ -- (ASSizeRange)constrainedSize; +- (ASSizeRange)constrainedSizeForKey:(NSString *)key; /** - * @abstract Subnodes in the new layout + * @abstract Retrieve the subnodes from either the "from" or "to" layout */ -- (NSArray *)subnodes; +- (NSArray *)subnodesForKey:(NSString *)key; /** * @abstract Subnodes that have been inserted in the layout transition diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e5eaa46c33..ca5879bea9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -610,34 +610,43 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - return [self measureWithSizeRange:constrainedSize completion:^(ASLayout *pendingLayout, ASSizeRange constrainedSize) { + return [self measureWithSizeRange:constrainedSize completion:^{ if ([[self class] usesImplicitHierarchyManagement]) { [self __implicitlyInsertSubnodes]; [self __implicitlyRemoveSubnodes]; } - [self __applyLayout:pendingLayout constrainedSize:constrainedSize]; + [self __completeLayoutCalculation]; }]; } -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)(ASLayout *, ASSizeRange))completion +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); if (![self __shouldSize]) return nil; - ASLayout *pendingLayout = _layout; - // only calculate the size if // - we haven't already // - the constrained size range is different if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) { - pendingLayout = [self calculateLayoutThatFits:constrainedSize]; - [self __calculateSubnodeOperationsWithPendingLayout:pendingLayout currentLayout:_layout]; - completion(pendingLayout, constrainedSize); + _previousLayout = _layout; + _layout = [self calculateLayoutThatFits:constrainedSize]; + + ASDisplayNodeAssertTrue(_layout.layoutableObject == self); + ASDisplayNodeAssertTrue(_layout.size.width >= 0.0); + ASDisplayNodeAssertTrue(_layout.size.height >= 0.0); + + _previousConstrainedSize = _constrainedSize; + _constrainedSize = constrainedSize; + + [self __calculateSubnodeOperationsWithLayout:_layout previousLayout:_previousLayout]; + _flags.isMeasured = YES; + + completion(); } - return pendingLayout; + return _layout; } - (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated @@ -648,50 +657,41 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated { - return [self measureWithSizeRange:constrainedSize completion:^(ASLayout *pendingLayout, ASSizeRange constrainedSize) { - _transitionContext = [[_ASTransitionContext alloc] initWithLayout:pendingLayout - constrainedSize:constrainedSize - animated:animated - delegate:self]; + return [self measureWithSizeRange:constrainedSize completion:^{ + _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; [self __implicitlyInsertSubnodes]; [self animateLayoutTransition:_transitionContext]; }]; } -- (void)__calculateSubnodeOperationsWithPendingLayout:(ASLayout *)pendingLayout currentLayout:(ASLayout *)currentLayout +- (void)__calculateSubnodeOperationsWithLayout:(ASLayout *)layout previousLayout:(ASLayout *)previousLayout { - if (_layout) { + if (previousLayout) { NSIndexSet *insertions, *deletions; - [currentLayout.immediateSublayouts asdk_diffWithArray:pendingLayout.immediateSublayouts + [previousLayout.immediateSublayouts asdk_diffWithArray:layout.immediateSublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - filterNodesInLayoutAtIndexes(pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); - filterNodesInLayoutAtIndexesWithIntersectingNodes(currentLayout, + filterNodesInLayoutAtIndexes(layout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); + filterNodesInLayoutAtIndexesWithIntersectingNodes(previousLayout, deletions, _insertedSubnodes, &_removedSubnodes, &_removedSubnodePositions); } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.immediateSublayouts count])]; - filterNodesInLayoutAtIndexes(pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [layout.immediateSublayouts count])]; + filterNodesInLayoutAtIndexes(layout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); _removedSubnodes = nil; } } -- (void)__applyLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize +- (void)__completeLayoutCalculation { - ASDisplayNodeAssertTrue(layout.layoutableObject == self); - ASDisplayNodeAssertTrue(layout.size.width >= 0.0); - ASDisplayNodeAssertTrue(layout.size.height >= 0.0); - - _layout = layout; - _constrainedSize = constrainedSize; - _flags.isMeasured = YES; _insertedSubnodes = nil; _removedSubnodes = nil; + _previousLayout = nil; [self calculatedLayoutDidChange]; // we generate placeholders at measureWithSizeRange: time so that a node is guaranteed @@ -777,7 +777,7 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (void)didCompleteTransitionLayout:(id)context { [self __implicitlyRemoveSubnodes]; - [self __applyLayout:context.layout constrainedSize:context.constrainedSize]; + [self __completeLayoutCalculation]; } #pragma mark - Implicit node hierarchy managagment @@ -814,6 +814,27 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( return _removedSubnodes; } +- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key +{ + if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { + return _previousLayout; + } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { + return _layout; + } else { + return nil; + } +} +- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key +{ + if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { + return _previousConstrainedSize; + } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { + return _constrainedSize; + } else { + return ASSizeRangeMake(CGSizeZero, CGSizeZero); + } +} + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { [self didCompleteTransitionLayout:context]; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 378ec535b0..f243fdee79 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -62,8 +62,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // This is the desired contentsScale, not the scale at which the layer's contents should be displayed CGFloat _contentsScaleForDisplay; + ASLayout *_previousLayout; ASLayout *_layout; + + ASSizeRange _previousConstrainedSize; ASSizeRange _constrainedSize; + UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index cf3ee5b10c..9411d76ac1 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -16,9 +16,13 @@ @protocol _ASTransitionContextDelegate - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context; + - (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context; - (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context; +- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key; +- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key; + - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; @end @@ -27,10 +31,6 @@ @property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated; -@property (strong, readonly) ASLayout *layout; - -@property (assign, readonly) ASSizeRange constrainedSize; - -- (instancetype)initWithLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; @end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index 753cac7b36..8c69f194fa 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -10,6 +10,10 @@ #import "ASLayout.h" + +NSString * const ASTransitionContextFromLayoutKey = @"org.asyncdisplaykit.ASTransitionContextFromLayoutKey"; +NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransitionContextToLayoutKey"; + @interface _ASTransitionContext () @property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate; @@ -18,12 +22,10 @@ @implementation _ASTransitionContext -- (instancetype)initWithLayout:(ASLayout *)layout constrainedSize:(ASSizeRange)constrainedSize animated:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate { self = [super init]; if (self) { - _layout = layout; - _constrainedSize = constrainedSize; _animated = animated; _delegate = delegate; } @@ -32,6 +34,16 @@ #pragma mark - ASContextTransitioning Protocol Implementation +- (ASLayout *)layoutForKey:(NSString *)key +{ + return [_delegate transitionContext:self layoutForKey:key]; +} + +- (ASSizeRange)constrainedSizeForKey:(NSString *)key +{ + return [_delegate transitionContext:self constrainedSizeForKey:key]; +} + - (CGRect)initialFrameForNode:(ASDisplayNode *)node { for (ASDisplayNode *subnode in [_delegate currentSubnodesWithTransitionContext:self]) { @@ -44,7 +56,7 @@ - (CGRect)finalFrameForNode:(ASDisplayNode *)node { - for (ASLayout *layout in _layout.sublayouts) { + for (ASLayout *layout in [self layoutForKey:ASTransitionContextToLayoutKey].sublayouts) { if (layout.layoutableObject == node) { return [layout frame]; } @@ -52,10 +64,10 @@ return CGRectZero; } -- (NSArray *)subnodes +- (NSArray *)subnodesForKey:(NSString *)key { NSMutableArray *subnodes = [NSMutableArray array]; - for (ASLayout *sublayout in _layout.immediateSublayouts) { + for (ASLayout *sublayout in [self layoutForKey:key].immediateSublayouts) { [subnodes addObject:(ASDisplayNode *)sublayout.layoutableObject]; } return subnodes; From 163ddb12400c563879f853a6e85d9302c871157f Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 15:13:46 -0800 Subject: [PATCH 111/224] Layout only immediate subnodes of a node --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ca5879bea9..5a4e65fafc 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2193,7 +2193,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)__layoutSublayouts { - for (ASLayout *subnodeLayout in _layout.sublayouts) { + for (ASLayout *subnodeLayout in _layout.immediateSublayouts) { ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; } } From 2e74a754927a755e3e86387e7fc36151c5932ced Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 15:16:26 -0800 Subject: [PATCH 112/224] Resize ASViewController node on view controller size transitions --- AsyncDisplayKit/ASViewController.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 4e93b70343..754652248c 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -10,6 +10,7 @@ #import "ASAssert.h" #import "ASDimension.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASDisplayNode+Beta.h" @implementation ASViewController { @@ -59,13 +60,20 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [_node measureWithSizeRange:[self nodeConstrainedSize]]; _ensureDisplayed = YES; [_node recursivelyFetchData]; } +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + [coordinator animateAlongsideTransition:^(id _Nonnull context) { + [self.node transitionLayoutWithSizeRange:ASSizeRangeMake(size, size) animated:[context isAnimated]]; + } completion:nil]; +} + // MARK: - Layout Helpers - (ASSizeRange)nodeConstrainedSize From cb7ce44bf2d135b21fa15d9d8c5a56ee429832e7 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 15:22:43 -0800 Subject: [PATCH 113/224] Revert updates to ASViewController --- AsyncDisplayKit/ASViewController.m | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 754652248c..6f052a5c60 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -48,6 +48,12 @@ self.view = _node.view; } +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + [_node measureWithSizeRange:[self nodeConstrainedSize]]; +} + - (void)viewDidLayoutSubviews { if (_ensureDisplayed && self.neverShowPlaceholders) { @@ -60,20 +66,10 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [_node measureWithSizeRange:[self nodeConstrainedSize]]; - _ensureDisplayed = YES; [_node recursivelyFetchData]; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [coordinator animateAlongsideTransition:^(id _Nonnull context) { - [self.node transitionLayoutWithSizeRange:ASSizeRangeMake(size, size) animated:[context isAnimated]]; - } completion:nil]; -} - // MARK: - Layout Helpers - (ASSizeRange)nodeConstrainedSize From 063cf865794fabd5dee4bce6745a7bcdda250438 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Wed, 10 Feb 2016 18:35:29 -0800 Subject: [PATCH 114/224] PINRemoteImage 2.0 moved its tag, who the hell are these idiots. -signed, author of PINRemoteImage --- AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 45f12cf004..256013739c 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -67,13 +67,13 @@ ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); if (progressBlock) { - [[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) { + [[PINRemoteImageManager sharedImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { dispatch_async(callbackQueue, ^{ progressBlock(result.image, result.UUID); }); } ofTaskWithUUID:downloadIdentifier]; } else { - [[PINRemoteImageManager sharedImageManager] setProgressCallback:nil ofTaskWithUUID:downloadIdentifier]; + [[PINRemoteImageManager sharedImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; } } From b267821d4ccc6069c675808888c9b8646f6d635c Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 10 Feb 2016 19:21:29 -0800 Subject: [PATCH 115/224] Wrap implicit node hierarchy management behind feature flag property --- AsyncDisplayKit/ASDisplayNode+Beta.h | 2 + AsyncDisplayKit/ASDisplayNode.mm | 42 +++++++++++-------- .../Private/ASDisplayNodeInternal.h | 1 + .../ASDisplayNodeImplicitHierarchyTests.m | 9 ++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 2642ba3f50..bad81b740a 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -39,6 +39,8 @@ /** @name Layout Transitioning */ +@property (nonatomic) BOOL usesImplicitHierarchyManagement; + /** * @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 5a4e65fafc..48bcc15d7d 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -32,7 +32,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; -@interface ASDisplayNode () +@interface ASDisplayNode () /** * @@ -54,12 +54,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS #define TIME_SCOPED(outVar) #endif -@interface ASDisplayNode () <_ASDisplayLayerDelegate, _ASTransitionContextDelegate> - -@property (assign, nonatomic) BOOL implicitNodeHierarchyManagement; - -@end - @implementation ASDisplayNode // these dynamic properties all defined in ASLayoutOptionsPrivate.m @@ -611,7 +605,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { return [self measureWithSizeRange:constrainedSize completion:^{ - if ([[self class] usesImplicitHierarchyManagement]) { + if (self.usesImplicitHierarchyManagement) { [self __implicitlyInsertSubnodes]; [self __implicitlyRemoveSubnodes]; } @@ -640,7 +634,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _previousConstrainedSize = _constrainedSize; _constrainedSize = constrainedSize; - [self __calculateSubnodeOperationsWithLayout:_layout previousLayout:_previousLayout]; + if (self.usesImplicitHierarchyManagement) { + [self __calculateSubnodeOperations]; + } _flags.isMeasured = YES; completion(); @@ -657,32 +653,34 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated { + _usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x return [self measureWithSizeRange:constrainedSize completion:^{ + _usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; [self __implicitlyInsertSubnodes]; [self animateLayoutTransition:_transitionContext]; }]; } -- (void)__calculateSubnodeOperationsWithLayout:(ASLayout *)layout previousLayout:(ASLayout *)previousLayout +- (void)__calculateSubnodeOperations { - if (previousLayout) { + if (_previousLayout) { NSIndexSet *insertions, *deletions; - [previousLayout.immediateSublayouts asdk_diffWithArray:layout.immediateSublayouts + [_previousLayout.immediateSublayouts asdk_diffWithArray:_layout.immediateSublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - filterNodesInLayoutAtIndexes(layout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); - filterNodesInLayoutAtIndexesWithIntersectingNodes(previousLayout, + filterNodesInLayoutAtIndexes(_layout, insertions, &_insertedSubnodes, &_insertedSubnodePositions); + filterNodesInLayoutAtIndexesWithIntersectingNodes(_previousLayout, deletions, _insertedSubnodes, &_removedSubnodes, &_removedSubnodePositions); } else { - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [layout.immediateSublayouts count])]; - filterNodesInLayoutAtIndexes(layout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_layout.immediateSublayouts count])]; + filterNodesInLayoutAtIndexes(_layout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); _removedSubnodes = nil; } } @@ -768,6 +766,16 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( #pragma mark - Layout Transition +- (BOOL)usesImplicitHierarchyManagement +{ + return _usesImplicitHierarchyManagement ?: [[self class] usesImplicitHierarchyManagement]; +} + +- (void)setUsesImplicitHierarchyManagement:(BOOL)value +{ + _usesImplicitHierarchyManagement = value; +} + - (void)animateLayoutTransition:(id)context { [self __layoutSublayouts]; @@ -1820,7 +1828,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) layout = [ASLayout layoutWithLayoutableObject:self size:layout.size sublayouts:@[layout]]; } return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { - if ([[self class] usesImplicitHierarchyManagement]) { + if (self.usesImplicitHierarchyManagement) { return ASObjectIsEqual(layout, evaluatedLayout) == NO && [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]]; } else { return [_subnodes containsObject:evaluatedLayout.layoutableObject]; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index f243fdee79..70686f9819 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -72,6 +72,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSMutableArray *_subnodes; _ASTransitionContext *_transitionContext; + BOOL _usesImplicitHierarchyManagement; NSArray *_insertedSubnodes; NSArray *_removedSubnodes; diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 0bb9fa327d..b54201dc8f 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -63,6 +63,15 @@ - (void)testFeatureFlag { XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]); + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + XCTAssert(node.usesImplicitHierarchyManagement); + + [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; + XCTAssertFalse([ASDisplayNode usesImplicitHierarchyManagement]); + XCTAssertFalse(node.usesImplicitHierarchyManagement); + + node.usesImplicitHierarchyManagement = YES; + XCTAssert(node.usesImplicitHierarchyManagement); } - (void)testInitialNodeInsertionWithOrdering From 3e56a327c7866f588d137346ab66bcd2cb01df3c Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 11 Feb 2016 10:47:27 -0800 Subject: [PATCH 116/224] Add constrainedSizeForNodeAtIndexPath to ASPagerNodeDataSource --- AsyncDisplayKit/ASPagerNode.h | 11 +++++++++++ AsyncDisplayKit/ASPagerNode.m | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 3596dfcad7..55b54941cc 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -50,6 +50,17 @@ */ - (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; +/** + * Provides the constrained size range for measuring the node at the index path. + * + * @param collectionView The sender. + * + * @param indexPath The index path of the node. + * + * @returns A constrained size range for layout the node at this index path. + */ +- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; + @end @interface ASPagerNode : ASCollectionNode diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 4d7ee51f9f..ea16ab7454 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -17,6 +17,7 @@ ASPagerNodeProxy *_proxy; __weak id _pagerDataSource; BOOL _pagerDataSourceImplementsNodeBlockAtIndex; + BOOL _pagerDataSourceImplementsConstrainedSizeForNode; } @end @@ -101,7 +102,10 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); + if (_pagerDataSourceImplementsConstrainedSizeForNode) { + return [_pagerDataSource pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; + } + return ASSizeRangeMake(CGSizeZero, self.bounds.size); } #pragma mark - Data Source Proxy @@ -115,9 +119,13 @@ { if (pagerDataSource != _pagerDataSource) { _pagerDataSource = pagerDataSource; + _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; // Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex: ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]); + + _pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; + _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; super.dataSource = (id )_proxy; From 3911fb1c34fa20b0f2bf038774be21fb8efc0f2d Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Thu, 11 Feb 2016 14:11:10 -0800 Subject: [PATCH 117/224] Adjust reloads' indexPaths in ChangeSet --- .../Private/_ASHierarchyChangeSet.h | 4 +- .../Private/_ASHierarchyChangeSet.m | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index a2547ff017..33d183a79d 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -30,8 +30,10 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; /// Index paths are sorted descending for changeType .Delete, ascending otherwise -@property (nonatomic, strong, readonly) NSArray *indexPaths; +@property (nonatomic, strong) NSArray *indexPaths; @property (nonatomic, readonly) _ASHierarchyChangeType changeType; + ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType; @end @interface _ASHierarchyChangeSet : NSObject diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m index bd820e9cd6..39047f867e 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m @@ -198,9 +198,41 @@ // Ignore item inserts in reloaded(new)/inserted sections. [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; + + + // reload itemsChanges need to be adjusted so that we access the correct indexPaths in the datasource + NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; + NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; + + for (_ASHierarchyItemChange *change in _reloadItemChanges) { + NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); + NSMutableArray *newIndexPaths = [NSMutableArray array]; + + // Every indexPaths in the change need to update its section and/or row + // depending on all the deletions and insertions + for (NSIndexPath *indexPath in change.indexPaths) { + NSUInteger section = indexPath.section; + NSUInteger row = indexPath.row; + + section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section)]; + section += [_insertedSections countOfIndexesInRange:NSMakeRange(0, section)]; + + NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section).stringValue]; + row += [indicesInsertedInSection countOfIndexesInRange:NSMakeRange(0, row)]; + NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(section).stringValue]; + row += [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row)]; + + //TODO: reuse the old indexPath object if section and row aren't changed + NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; + [newIndexPaths addObject:newIndexPath]; + } + change.indexPaths = newIndexPaths; + } } } + + @end @implementation _ASHierarchySectionChange @@ -301,6 +333,28 @@ return self; } +// Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion +// e.g. changes: (0 - 0), (0 - 1), (2 - 5) +// will become: {@0 : [0, 1], @2 : [5]} ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType +{ + NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary]; + for (_ASHierarchyItemChange *change in changes) { + NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); + for (NSIndexPath *indexPath in change.indexPaths) { + NSString *sectionKey = @(indexPath.section).stringValue; + NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; + if (indexSet) { + [indexSet addIndex:indexPath.row]; + } else { + indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.row]; + sectionToIndexSetMap[sectionKey] = indexSet; + } + } + } + return sectionToIndexSetMap; +} + + (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections { if (changes.count < 1) { From 88ff693327cee3f851d4b145b4d1d307cd02af64 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Thu, 11 Feb 2016 23:49:14 -0800 Subject: [PATCH 118/224] Fixes in ChangeSet and DataController --- .../Details/ASChangeSetDataController.m | 24 +++++++++-------- .../Details/ASDataController+Subclasses.h | 8 ++++++ AsyncDisplayKit/Details/ASDataController.mm | 11 +++++--- .../Private/_ASHierarchyChangeSet.h | 4 ++- .../Private/_ASHierarchyChangeSet.m | 26 ++++++++++++------- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 3e2c2ce118..6448b9ec47 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -11,6 +11,8 @@ #import "_ASHierarchyChangeSet.h" #import "ASAssert.h" +#import "ASDataController+Subclasses.h" + @interface ASChangeSetDataController () @property (nonatomic, assign) NSUInteger batchUpdateCounter; @@ -51,15 +53,7 @@ [_changeSet markCompleted]; [super beginUpdates]; - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - + for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; } @@ -67,7 +61,15 @@ for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; } - + + for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadSections:change.indexSet withAnimationOptions:change.animationOptions]; + } + + for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadRowsAtIndexPaths:change.indexPaths withIndexPathsAfterUpdates:change.indexPathsAfterUpdates withAnimationOptions:change.animationOptions]; + } + for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { [super insertSections:change.indexSet withAnimationOptions:change.animationOptions]; } @@ -75,7 +77,7 @@ for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; } - + [super endUpdatesAnimated:animated completion:completion]; _changeSet = nil; diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..18b4b35ff6 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -50,6 +50,14 @@ */ - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; +#pragma mark - Row Editing (Internal API) + +/** + * reload a set of IndexPaths requires deleting the cells at their current indexPaths + * and then inserting new ones at their future indexPaths, and these two sets of indexPath will be different in many cases + */ +- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withIndexPathsAfterUpdates:(NSArray *)indexPathAfterUpdates withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + #pragma mark - Node & Section Insertion/Deletion API /** diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index ddc821f133..b60ea3b44a 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -787,6 +787,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self reloadRowsAtIndexPaths:indexPaths withIndexPathsAfterUpdates:indexPaths withAnimationOptions:animationOptions]; +} + +- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withIndexPathsAfterUpdates:(NSArray *)indexPathAfterUpdates withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -796,20 +801,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPathAfterUpdates.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - for (NSIndexPath *indexPath in indexPaths) { + for (NSIndexPath *indexPath in indexPathAfterUpdates) { [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPathAfterUpdates withAnimationOptions:animationOptions]; }]; }]; }]; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index 33d183a79d..c97f3f4e3d 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -30,7 +30,9 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; /// Index paths are sorted descending for changeType .Delete, ascending otherwise -@property (nonatomic, strong) NSArray *indexPaths; +@property (nonatomic, strong, readonly) NSArray *indexPaths; +/// Calculated indexPaths after any insertions or deletions +@property (nonatomic, strong) NSArray *indexPathsAfterUpdates; @property (nonatomic, readonly) _ASHierarchyChangeType changeType; + (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m index 39047f867e..3a76f3c4ec 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m @@ -198,9 +198,8 @@ // Ignore item inserts in reloaded(new)/inserted sections. [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; - - // reload itemsChanges need to be adjusted so that we access the correct indexPaths in the datasource + // reload items changes need to be adjusted so that we access the correct indexPaths in the datasource NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; @@ -210,23 +209,30 @@ // Every indexPaths in the change need to update its section and/or row // depending on all the deletions and insertions + // For reference, when batching reloads/deletes/inserts: + // - delete/reload indexPaths that are passed in should all be their current indexPaths + // - insert indexPaths that are passed in should all be their future indexPaths after deletions for (NSIndexPath *indexPath in change.indexPaths) { NSUInteger section = indexPath.section; NSUInteger row = indexPath.row; - section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section)]; - section += [_insertedSections countOfIndexesInRange:NSMakeRange(0, section)]; - NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section).stringValue]; - row += [indicesInsertedInSection countOfIndexesInRange:NSMakeRange(0, row)]; - NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(section).stringValue]; - row += [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row)]; + // Update section number based on section insertions/deletions that are above the current section + section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section + 1)]; + section += [_insertedSections countOfIndexesInRange:NSMakeRange(0, section + 1)]; + // Update row number based on deletions that are above the current row in the current section + NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)]; + row -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row + 1)]; + // Update row number based on insertions that are above the current row in the future section + NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)]; + row += [indicesInsertedInSection countOfIndexesInRange:NSMakeRange(0, row + 1)]; + //TODO: reuse the old indexPath object if section and row aren't changed NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; [newIndexPaths addObject:newIndexPath]; } - change.indexPaths = newIndexPaths; + change.indexPathsAfterUpdates = newIndexPaths; } } } @@ -342,7 +348,7 @@ for (_ASHierarchyItemChange *change in changes) { NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); for (NSIndexPath *indexPath in change.indexPaths) { - NSString *sectionKey = @(indexPath.section).stringValue; + NSString *sectionKey = @(indexPath.section); NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; if (indexSet) { [indexSet addIndex:indexPath.row]; From 5b135e1837df947088693e7a32a5135296401aec Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 11 Feb 2016 23:59:41 -0800 Subject: [PATCH 119/224] ASPagerNode uses its view's size for node constrained sizes --- AsyncDisplayKit/ASPagerNode.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index ea16ab7454..74aa5b6527 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -105,7 +105,7 @@ if (_pagerDataSourceImplementsConstrainedSizeForNode) { return [_pagerDataSource pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; } - return ASSizeRangeMake(CGSizeZero, self.bounds.size); + return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); } #pragma mark - Data Source Proxy From bb99de4e0fbbd6808718bb535a7f306ddece8bf8 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 12 Feb 2016 08:20:47 -0800 Subject: [PATCH 120/224] Fix some logic issues in ASHierarchyChangeSet --- .../Private/_ASHierarchyChangeSet.m | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m index 3a76f3c4ec..389bf0e5f9 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m @@ -106,8 +106,15 @@ return NSNotFound; } - NSInteger indexAfterDeletes = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)]; - return indexAfterDeletes + [_insertedSections countOfIndexesInRange:NSMakeRange(0, indexAfterDeletes)]; + __block NSInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)]; + [_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + if (idx <= newIndex) { + newIndex += 1; + } else { + *stop = YES; + } + }]; + return newIndex; } - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options @@ -213,20 +220,32 @@ // - delete/reload indexPaths that are passed in should all be their current indexPaths // - insert indexPaths that are passed in should all be their future indexPaths after deletions for (NSIndexPath *indexPath in change.indexPaths) { - NSUInteger section = indexPath.section; - NSUInteger row = indexPath.row; + __block NSUInteger section = indexPath.section; + __block NSUInteger row = indexPath.row; // Update section number based on section insertions/deletions that are above the current section - section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section + 1)]; - section += [_insertedSections countOfIndexesInRange:NSMakeRange(0, section + 1)]; + section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section)]; + [_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + if (idx <= section) { + section += 1; + } else { + *stop = YES; + } + }]; // Update row number based on deletions that are above the current row in the current section NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)]; - row -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row + 1)]; + row -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row)]; // Update row number based on insertions that are above the current row in the future section NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)]; - row += [indicesInsertedInSection countOfIndexesInRange:NSMakeRange(0, row + 1)]; + [indicesInsertedInSection enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + if (idx <= row) { + row += 1; + } else { + *stop = YES; + } + }]; //TODO: reuse the old indexPath object if section and row aren't changed NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; @@ -348,7 +367,7 @@ for (_ASHierarchyItemChange *change in changes) { NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); for (NSIndexPath *indexPath in change.indexPaths) { - NSString *sectionKey = @(indexPath.section); + NSNumber *sectionKey = @(indexPath.section); NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; if (indexSet) { [indexSet addIndex:indexPath.row]; From 66c8c8f47d6b1b4d112a579ab0636949ead4d956 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 12 Feb 2016 13:44:41 -0800 Subject: [PATCH 121/224] Add rotation support to ASPagerNode --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 +++++ AsyncDisplayKit/ASPagerFlowLayout.h | 13 +++++ AsyncDisplayKit/ASPagerFlowLayout.m | 63 +++++++++++++++++++++++ AsyncDisplayKit/ASPagerNode.h | 4 +- AsyncDisplayKit/ASPagerNode.m | 9 ++-- AsyncDisplayKit/AsyncDisplayKit.h | 1 + 6 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 AsyncDisplayKit/ASPagerFlowLayout.h create mode 100644 AsyncDisplayKit/ASPagerFlowLayout.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 081786c9ee..285d1b7ad2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -471,6 +471,10 @@ DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */; }; + DBDB83941C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DBDB83961C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; + DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; @@ -794,6 +798,8 @@ DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeImplicitHierarchyTests.m; sourceTree = ""; }; + DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerFlowLayout.h; sourceTree = ""; }; + DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerFlowLayout.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; @@ -918,6 +924,8 @@ 058D09B1195D04C000B7D73C /* AsyncDisplayKit */ = { isa = PBXGroup; children = ( + DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, + DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */, 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, @@ -1360,6 +1368,7 @@ 257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */, 058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */, 058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */, + DBDB83941C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */, 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */, 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, @@ -1495,6 +1504,7 @@ 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, + DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */, 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, @@ -1747,6 +1757,7 @@ 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */, + DBDB83961C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */, 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */, AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, @@ -1898,6 +1909,7 @@ B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, + DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, diff --git a/AsyncDisplayKit/ASPagerFlowLayout.h b/AsyncDisplayKit/ASPagerFlowLayout.h new file mode 100644 index 0000000000..9b784107a6 --- /dev/null +++ b/AsyncDisplayKit/ASPagerFlowLayout.h @@ -0,0 +1,13 @@ +// +// ASPagerFlowLayout.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface ASPagerFlowLayout : UICollectionViewFlowLayout + +@end diff --git a/AsyncDisplayKit/ASPagerFlowLayout.m b/AsyncDisplayKit/ASPagerFlowLayout.m new file mode 100644 index 0000000000..2118a394f9 --- /dev/null +++ b/AsyncDisplayKit/ASPagerFlowLayout.m @@ -0,0 +1,63 @@ +// +// ASPagerFlowLayout.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASPagerFlowLayout.h" + +@interface ASPagerFlowLayout () + +@property (strong, nonatomic) NSIndexPath *currentIndexPath; + +@end + +@implementation ASPagerFlowLayout + +- (void)invalidateLayout +{ + self.currentIndexPath = [self _indexPathForVisiblyCenteredItem]; + [super invalidateLayout]; +} + +- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset +{ + if (self.currentIndexPath) { + return [self _targetContentOffsetForItemAtIndexPath:self.currentIndexPath + proposedContentOffset:proposedContentOffset]; + } + + return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; +} + +- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset +{ + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:self.currentIndexPath]; + CGFloat xOffset = (self.collectionView.bounds.size.width - attributes.frame.size.width) / 2; + return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); +} + +- (NSIndexPath *)_indexPathForVisiblyCenteredItem +{ + CGRect visibleRect = [self _visibleRect]; + CGFloat visibleXCenter = CGRectGetMidX(visibleRect); + NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; + for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { + if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { + return attributes.indexPath; + } + } + return nil; +} + +- (CGRect)_visibleRect +{ + CGRect visibleRect; + visibleRect.origin = self.collectionView.contentOffset; + visibleRect.size = self.collectionView.bounds.size; + return visibleRect; +} + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 55b54941cc..ea237ef5a1 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -9,6 +9,8 @@ #import @class ASPagerNode; +@class ASPagerFlowLayout; + @protocol ASPagerNodeDataSource /** @@ -69,7 +71,7 @@ - (instancetype)init; // Initializer with custom-configured flow layout properties. -- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; // Data Source is required, and uses a different protocol from ASCollectionNode. - (void)setDataSource:(id )dataSource; diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 74aa5b6527..52e33bc8fa 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -9,11 +9,12 @@ #import "ASPagerNode.h" #import "ASDelegateProxy.h" #import "ASDisplayNode+Subclasses.h" +#import "ASPagerFlowLayout.h" #import "UICollectionViewLayout+ASConvenience.h" @interface ASPagerNode () { - UICollectionViewFlowLayout *_flowLayout; + ASPagerFlowLayout *_flowLayout; ASPagerNodeProxy *_proxy; __weak id _pagerDataSource; BOOL _pagerDataSourceImplementsNodeBlockAtIndex; @@ -27,7 +28,7 @@ - (instancetype)init { - UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init]; flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumLineSpacing = 0; @@ -35,9 +36,9 @@ return [self initWithCollectionViewLayout:flowLayout]; } -- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; { - ASDisplayNodeAssert([flowLayout asdk_isFlowLayout], @"ASPagerNode requires a flow layout."); + ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 9affcc7b37..196bc1eb82 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -30,6 +30,7 @@ #import +#import #import #import From cf1e4c87f56acf287b806428df1c228ea0cad61e Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 12 Feb 2016 15:04:14 -0800 Subject: [PATCH 122/224] Add image loading delegate method / cache instead of respondsToSelector --- AsyncDisplayKit/ASNetworkImageNode.h | 9 ++++++++ AsyncDisplayKit/ASNetworkImageNode.mm | 32 ++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 82513a8c55..0579b0351a 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -95,6 +95,15 @@ NS_ASSUME_NONNULL_BEGIN @optional +/** + * Notification that the image node started to load + * + * @param imageNode The sender. + * + * @discussion Called on a background queue. + */ +- (void)imageNodeDidStartLoading:(ASNetworkImageNode *)imageNode; + /** * Notification that the image node failed to download the image. * diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index debeab01f7..6c152dd06b 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -35,10 +35,15 @@ BOOL _imageLoaded; + BOOL _delegateSupportsDidStartLoading; + BOOL _delegateSupportsDidFailWithError; + BOOL _delegateSupportsImageNodeDidFinishDecoding; + //set on init only BOOL _downloaderSupportsNewProtocol; BOOL _downloaderImplementsSetProgress; BOOL _downloaderImplementsSetPriority; + BOOL _cacheSupportsNewProtocol; BOOL _cacheSupportsClearing; } @@ -144,6 +149,10 @@ { ASDN::MutexLocker l(_lock); _delegate = delegate; + + _delegateSupportsDidStartLoading = [delegate respondsToSelector:@selector(imageNodeDidStartLoading:)]; + _delegateSupportsDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; + _delegateSupportsImageNodeDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; } - (id)delegate @@ -278,6 +287,13 @@ - (void)_lazilyLoadImageIfNecessary { if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { + { + ASDN::MutexLocker l(_lock); + if (_delegateSupportsDidStartLoading) { + [_delegate imageNodeDidStartLoading:self]; + } + } + if (_URL.isFileURL) { { ASDN::MutexLocker l(_lock); @@ -329,11 +345,14 @@ strongSelf->_cacheUUID = nil; } - if (responseImage != NULL) { - [strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image]; - } - else if (error && [strongSelf->_delegate respondsToSelector:@selector(imageNode:didFailWithError:)]) { - [strongSelf->_delegate imageNode:strongSelf didFailWithError:error]; + { + ASDN::MutexLocker l(strongSelf->_lock); + if (responseImage != NULL) { + [strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image]; + } + else if (error && _delegateSupportsDidFailWithError) { + [strongSelf->_delegate imageNode:strongSelf didFailWithError:error]; + } } }; @@ -377,7 +396,8 @@ - (void)asyncdisplaykit_asyncTransactionContainerStateDidChange { if (self.asyncdisplaykit_asyncTransactionContainerState == ASAsyncTransactionContainerStateNoTransactions) { - if (self.layer.contents != nil && [self.delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]) { + ASDN::MutexLocker l(_lock); + if (self.layer.contents != nil && _delegateSupportsImageNodeDidFinishDecoding) { [self.delegate imageNodeDidFinishDecoding:self]; } } From 7d80fb31bd2f97c6f795697a721f78623d63fdc8 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 12 Feb 2016 15:10:58 -0800 Subject: [PATCH 123/224] rename imageNodeDidStartLoading to imageNodeDidStartFetchingData --- AsyncDisplayKit/ASNetworkImageNode.h | 2 +- AsyncDisplayKit/ASNetworkImageNode.mm | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 0579b0351a..79070aaccc 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -102,7 +102,7 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion Called on a background queue. */ -- (void)imageNodeDidStartLoading:(ASNetworkImageNode *)imageNode; +- (void)imageNodeDidStartFetchingData:(ASNetworkImageNode *)imageNode; /** * Notification that the image node failed to download the image. diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 6c152dd06b..3b5eb75db9 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -35,7 +35,7 @@ BOOL _imageLoaded; - BOOL _delegateSupportsDidStartLoading; + BOOL _delegateSupportsDidStartFetchingData; BOOL _delegateSupportsDidFailWithError; BOOL _delegateSupportsImageNodeDidFinishDecoding; @@ -150,7 +150,7 @@ ASDN::MutexLocker l(_lock); _delegate = delegate; - _delegateSupportsDidStartLoading = [delegate respondsToSelector:@selector(imageNodeDidStartLoading:)]; + _delegateSupportsDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; _delegateSupportsDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; _delegateSupportsImageNodeDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; } @@ -289,8 +289,8 @@ if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { { ASDN::MutexLocker l(_lock); - if (_delegateSupportsDidStartLoading) { - [_delegate imageNodeDidStartLoading:self]; + if (_delegateSupportsDidStartFetchingData) { + [_delegate imageNodeDidStartFetchingData:self]; } } From fce722b0c8cb457b274d1edb45cf11afa966404e Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 13 Feb 2016 00:58:56 -0800 Subject: [PATCH 124/224] [ASDisplayNode] Allow display-scheduling method to be called concurrently. --- AsyncDisplayKit/ASDisplayNode.mm | 40 +++++++++++++-------- AsyncDisplayKit/Details/ASDataController.mm | 1 - 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 48bcc15d7d..eef689b8da 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -219,29 +219,41 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node { - ASDisplayNodeAssertMainThread(); - static NSMutableSet *nodesToDisplay = nil; - static BOOL displayScheduled = NO; - static ASDN::RecursiveMutex displaySchedulerLock; + static ASDN::RecursiveMutex __displaySchedulerLock; + static NSMutableArray *__nodesToDisplay = nil; + static BOOL __displayScheduled = NO; + BOOL scheduleDisplayPassNow = NO; { - ASDN::MutexLocker l(displaySchedulerLock); - if (!nodesToDisplay) { - nodesToDisplay = [[NSMutableSet alloc] init]; + ASDN::MutexLocker l(__displaySchedulerLock); + + if (!__nodesToDisplay) { + __nodesToDisplay = [NSMutableArray array]; + } + + if ([__nodesToDisplay indexOfObjectIdenticalTo:node] == NSNotFound) { + [__nodesToDisplay addObject:node]; + } + + if (!__displayScheduled) { + scheduleDisplayPassNow = YES; + __displayScheduled = YES; } - [nodesToDisplay addObject:node]; } - if (!displayScheduled) { - displayScheduled = YES; + if (scheduleDisplayPassNow) { // It's essenital that any layout pass that is scheduled during the current // runloop has a chance to be applied / scheduled, so always perform this after the current runloop. dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(displaySchedulerLock); - displayScheduled = NO; - NSSet *displayingNodes = [nodesToDisplay copy]; + NSArray *displayingNodes = nil; + // Create a lock scope. Snatch the waiting nodes, let the next batch create a new container. + { + ASDN::MutexLocker l(__displaySchedulerLock); + displayingNodes = [__nodesToDisplay copy]; + __nodesToDisplay = nil; + __displayScheduled = NO; + } CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent(); - nodesToDisplay = nil; for (ASDisplayNode *node in displayingNodes) { [node __recursivelyTriggerDisplayAndBlock:NO]; } diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index b60ea3b44a..a0d3efeb61 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -168,7 +168,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - dispatch_block_t allocationBlock = ^{ for (NSUInteger k = j; k < j + batchCount; k++) { ASCellNodeBlock cellBlock = nodes[k]; From a45975220574ff615bb7c1ea93a717b363b210ce Mon Sep 17 00:00:00 2001 From: Dafeng Jin Date: Tue, 16 Feb 2016 10:30:39 +0800 Subject: [PATCH 125/224] Update ItemNode.m addSubnode self.soldOutOverlay twice --- examples/CatDealsCollectionView/Sample/ItemNode.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index 15608a7620..dd1c27a290 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -127,8 +127,6 @@ const CGFloat kSoldOutGBHeight = 50.0; self.soldOutLabelBackground.hidden = YES; self.soldOutLabelFlat.hidden = YES; - [self addSubnode:self.soldOutOverlay]; - if ([ItemNode isRTL]) { self.titleLabel.alignSelf = ASStackLayoutAlignSelfEnd; self.firstInfoLabel.alignSelf = ASStackLayoutAlignSelfEnd; From 23019cbbea54210f295fc4c4fad80f4dc27b5c9e Mon Sep 17 00:00:00 2001 From: rcancro Date: Tue, 16 Feb 2016 09:34:38 -0800 Subject: [PATCH 126/224] Remove wordkerner in ASTextNode to fix jumbled text bug --- AsyncDisplayKit/ASTextNode.mm | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 99f8f716d7..a2b9f3121a 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -21,7 +21,6 @@ #import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitShadower.h" -#import "ASTextNodeWordKerner.h" #import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" @@ -81,10 +80,6 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation ASTextKitRenderer *_renderer; UILongPressGestureRecognizer *_longPressGestureRecognizer; - - ASDN::Mutex _wordKernerLock; - ASTextNodeWordKerner *_wordKerner; - } @dynamic placeholderEnabled; @@ -248,7 +243,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, .minimumScaleFactor = _minimumScaleFactor, - .layoutManagerDelegate = [self _wordKerner], }; } @@ -284,15 +278,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } } -- (ASTextNodeWordKerner *)_wordKerner -{ - ASDN::MutexLocker l(_wordKernerLock); - if (_wordKerner == nil) { - _wordKerner = [[ASTextNodeWordKerner alloc] init]; - } - return _wordKerner; -} - #pragma mark - Layout and Sizing - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize From db1a91234d703c1b675bc02648c6a86a14c462b3 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 16 Feb 2016 10:57:12 -0800 Subject: [PATCH 127/224] Improve reload handling in BatchUpdates --- .../Details/ASChangeSetDataController.m | 2 +- .../Details/ASDataController+Subclasses.h | 8 ----- AsyncDisplayKit/Details/ASDataController.mm | 11 ++----- .../Private/_ASHierarchyChangeSet.h | 3 +- .../Private/_ASHierarchyChangeSet.m | 31 +++++++++++++------ 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 6448b9ec47..666ea86ce7 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -67,7 +67,7 @@ } for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadRowsAtIndexPaths:change.indexPaths withIndexPathsAfterUpdates:change.indexPathsAfterUpdates withAnimationOptions:change.animationOptions]; + [super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; } for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 18b4b35ff6..32d787910e 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -50,14 +50,6 @@ */ - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -#pragma mark - Row Editing (Internal API) - -/** - * reload a set of IndexPaths requires deleting the cells at their current indexPaths - * and then inserting new ones at their future indexPaths, and these two sets of indexPath will be different in many cases - */ -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withIndexPathsAfterUpdates:(NSArray *)indexPathAfterUpdates withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - #pragma mark - Node & Section Insertion/Deletion API /** diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index a0d3efeb61..a792eedc6d 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -786,11 +786,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self reloadRowsAtIndexPaths:indexPaths withIndexPathsAfterUpdates:indexPaths withAnimationOptions:animationOptions]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withIndexPathsAfterUpdates:(NSArray *)indexPathAfterUpdates withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -800,20 +795,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPathAfterUpdates.count]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - for (NSIndexPath *indexPath in indexPathAfterUpdates) { + for (NSIndexPath *indexPath in indexPaths) { [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPathAfterUpdates withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index c97f3f4e3d..42c2fa71ab 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -31,8 +31,7 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { /// Index paths are sorted descending for changeType .Delete, ascending otherwise @property (nonatomic, strong, readonly) NSArray *indexPaths; -/// Calculated indexPaths after any insertions or deletions -@property (nonatomic, strong) NSArray *indexPathsAfterUpdates; + @property (nonatomic, readonly) _ASHierarchyChangeType changeType; + (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m index 389bf0e5f9..07ef6b2ddb 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m @@ -192,24 +192,23 @@ NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy]; // Get the new section that each reloaded section index corresponds to. + // Coalesce reload sections' indexes into deletes and inserts [_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) { NSUInteger newIndex = [self newSectionForOldSection:oldIndex]; if (newIndex != NSNotFound) { [insertedOrReloaded addIndex:newIndex]; } + [deletedOrReloaded addIndex:oldIndex]; }]; - // Ignore item reloads/deletes in reloaded/deleted sections. - [_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded]; - [_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:deletedOrReloaded]; - - // Ignore item inserts in reloaded(new)/inserted sections. - [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; + _deletedSections = deletedOrReloaded; + _insertedSections = insertedOrReloaded; + _reloadedSections = nil; // reload items changes need to be adjusted so that we access the correct indexPaths in the datasource NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; - + for (_ASHierarchyItemChange *change in _reloadItemChanges) { NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); NSMutableArray *newIndexPaths = [NSMutableArray array]; @@ -246,13 +245,27 @@ *stop = YES; } }]; - + //TODO: reuse the old indexPath object if section and row aren't changed NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; [newIndexPaths addObject:newIndexPath]; } - change.indexPathsAfterUpdates = newIndexPaths; + + // All reload changes are coalesced into deletes and inserts + // We delete the items that needs reload together with other deleted items, at their original index + _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO]; + [_deleteItemChanges addObject:deleteItemChangeFromReloadChange]; + // We insert the items that needs reload together with other inserted items, at their future index + _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO]; + [_insertItemChanges addObject:insertItemChangeFromReloadChange]; } + [_reloadItemChanges removeAllObjects]; + + // Ignore item deletes in reloaded/deleted sections. + [_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded]; + + // Ignore item inserts in reloaded(new)/inserted sections. + [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; } } From 74661e27f674cc7711cd8ead8fe3d1bc564946a8 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 16 Feb 2016 13:42:56 -0800 Subject: [PATCH 128/224] Only start a fading animation if we're visible. And a threadsafety fix. --- AsyncDisplayKit/ASDisplayNode.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 48bcc15d7d..8f91f358c7 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -768,11 +768,13 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (BOOL)usesImplicitHierarchyManagement { + ASDN::MutexLocker l(_propertyLock); return _usesImplicitHierarchyManagement ?: [[self class] usesImplicitHierarchyManagement]; } - (void)setUsesImplicitHierarchyManagement:(BOOL)value { + ASDN::MutexLocker l(_propertyLock); _usesImplicitHierarchyManagement = value; } @@ -1706,7 +1708,7 @@ static NSInteger incrementIfFound(NSInteger i) { [self _tearDownPlaceholderLayer]; }; - if (_placeholderFadeDuration > 0.0) { + if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { [CATransaction begin]; [CATransaction setCompletionBlock:cleanupBlock]; [CATransaction setAnimationDuration:_placeholderFadeDuration]; From a920e353c63168c00ce57a2202adbbde2beee9d4 Mon Sep 17 00:00:00 2001 From: rcancro Date: Fri, 12 Feb 2016 11:11:06 -0800 Subject: [PATCH 129/224] adjust font size to make text fit within constrained size # Conflicts: # AsyncDisplayKit/ASTextNode.mm --- AsyncDisplayKit/ASTextNode+Beta.h | 6 +- AsyncDisplayKit/ASTextNode.mm | 16 +- AsyncDisplayKit/TextKit/ASTextKitAttributes.h | 18 +- .../TextKit/ASTextKitFontSizeAdjuster.h | 46 ++++- .../TextKit/ASTextKitFontSizeAdjuster.m | 98 ---------- .../TextKit/ASTextKitFontSizeAdjuster.mm | 185 ++++++++++++++++++ AsyncDisplayKit/TextKit/ASTextKitRenderer.h | 2 + AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 42 +++- 8 files changed, 284 insertions(+), 129 deletions(-) delete mode 100644 AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m create mode 100644 AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 8c9bd0286c..38059aa7c3 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -10,9 +10,9 @@ @interface ASTextNode () /** - @abstract The minimum scale that the textnode can apply to fit long words. - @default 0 (No scaling) + @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size + @default nil (no scaling) */ -@property (nonatomic, assign) CGFloat minimumScaleFactor; +@property (nonatomic, copy) NSArray *pointSizeScaleFactors; @end \ No newline at end of file diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index a2b9f3121a..a6c17e5bf0 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -18,6 +18,7 @@ #import "ASTextKitCoreTextAdditions.h" #import "ASTextKitHelpers.h" +#import "ASTextKitFontSizeAdjuster.h" #import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitShadower.h" @@ -78,6 +79,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation CGSize _constrainedSize; ASTextKitRenderer *_renderer; + CGFloat _currentScaleFactor; UILongPressGestureRecognizer *_longPressGestureRecognizer; } @@ -242,7 +244,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .lineBreakMode = _truncationMode, .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, - .minimumScaleFactor = _minimumScaleFactor, + .pointSizeScaleFactors = _pointSizeScaleFactors, + .currentScaleFactor = _currentScaleFactor, }; } @@ -256,6 +259,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; ASPerformBlockOnBackgroundThread(^{ + // before we remove the renderer, take its scale factor so we set it when a new renderer is created + _currentScaleFactor = renderer.currentScaleFactor; renderer = nil; }); _renderer = nil; @@ -1059,16 +1064,15 @@ static NSAttributedString *DefaultTruncationAttributedString() return visibleRange.length < _attributedString.length; } -- (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor +- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { - if (_minimumScaleFactor != minimumScaleFactor) { - _minimumScaleFactor = minimumScaleFactor; + if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { + _pointSizeScaleFactors = pointSizeScaleFactors; [self _invalidateRenderer]; ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ [self setNeedsDisplay]; }); - } -} + }} - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index d7bf44fef2..4d2160ea33 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -58,6 +58,7 @@ struct ASTextKitAttributes { NSLineBreakMode lineBreakMode; /** The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum. + This is required to apply scale factors to shrink text to fit within a number of lines */ NSUInteger maximumNumberOfLines; /** @@ -82,9 +83,13 @@ struct ASTextKitAttributes { */ CGFloat shadowRadius; /** - The minimum scale that the textnode can apply to fit long words in constrained size. + An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size. */ - CGFloat minimumScaleFactor; + NSArray *pointSizeScaleFactors; + /** + The currently applied scale factor. Only valid if pointSizeScaleFactors are provided. Defaults to 0 (no scaling) + */ + CGFloat currentScaleFactor; /** A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. */ @@ -112,8 +117,10 @@ struct ASTextKitAttributes { [shadowColor copy], shadowOpacity, shadowRadius, - minimumScaleFactor, - layoutManagerFactory + pointSizeScaleFactors, + currentScaleFactor, + layoutManagerFactory, + layoutManagerDelegate, }; }; @@ -124,7 +131,8 @@ struct ASTextKitAttributes { && maximumNumberOfLines == other.maximumNumberOfLines && shadowOpacity == other.shadowOpacity && shadowRadius == other.shadowRadius - && minimumScaleFactor == other.minimumScaleFactor + && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] + && currentScaleFactor == currentScaleFactor && layoutManagerFactory == other.layoutManagerFactory && CGSizeEqualToSize(shadowOffset, other.shadowOffset) && _objectsEqual(exclusionPaths, other.exclusionPaths) diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h index 84ba5fa367..cd90726dd0 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.h @@ -1,20 +1,46 @@ -// -// ASTextKitFontSizeAdjuster.h -// AsyncDisplayKit -// -// Created by Luke on 1/20/16. -// Copyright © 2016 Facebook. All rights reserved. -// +/* 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 +#import "ASTextKitAttributes.h" +#import "ASTextKitContext.h" @interface ASTextKitFontSizeAdjuster : NSObject @property (nonatomic, assign) CGSize constrainedSize; +/** + * Creates a class that will return a scale factor the will make a string fit inside the constrained size. + * + * "Fitting" means that both the longest word in the string will fit without breaking in the constrained + * size's width AND that the entire string will try to fit within attribute's maximumLineCount. The amount + * that the string will scale is based upon the attribute's pointSizeScaleFactors. If the string cannot fit + * in the given width/number of lines, the smallest scale factor will be returned. + * + * @param context The text kit context + * @param constrainedSize The constrained size to render into + * @param textComponentAttributes The renderer's text attributes + */ - (instancetype)initWithContext:(ASTextKitContext *)context - minimumScaleFactor:(CGFloat)minimumScaleFactor - constrainedSize:(CGSize)constrainedSize; + constrainedSize:(CGSize)constrainedSize + textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; + +/** + * Returns the best fit scale factor for the text + */ +- (CGFloat)scaleFactor; + +/** + * Takes all of the attributed string attributes dealing with size (font size, line spacing, kerning, etc) and + * scales them by the scaleFactor. I wouldn't be surprised if I missed some in here. + */ ++ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor; -- (void) adjustFontSize; @end + + diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m deleted file mode 100644 index 731c19c208..0000000000 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTextKitFontSizeAdjuster.m -// AsyncDisplayKit -// -// Created by Luke on 1/20/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "ASTextKitContext.h" -#import "ASTextKitFontSizeAdjuster.h" - -@implementation ASTextKitFontSizeAdjuster -{ - __weak ASTextKitContext *_context; - CGFloat _minimumScaleFactor; -} - -- (instancetype)initWithContext:(ASTextKitContext *)context - minimumScaleFactor:(CGFloat)minimumScaleFactor - constrainedSize:(CGSize)constrainedSize -{ - if (self = [super init]) { - _context = context; - _minimumScaleFactor = minimumScaleFactor; - _constrainedSize = constrainedSize; - } - return self; -} - -- (CGSize)sizeForAttributedString:(NSAttributedString *)attrString -{ - return [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) - options:NSStringDrawingUsesLineFragmentOrigin - context:nil].size; -} - - -- (void) adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor -{ - { - [attrString beginEditing]; - - [attrString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { - - UIFont* font = value; - font = [font fontWithSize:font.pointSize * scaleFactor]; - - [attrString removeAttribute:NSFontAttributeName range:range]; - [attrString addAttribute:NSFontAttributeName value:font range:range]; - }]; - - [attrString endEditing]; - } -} - - -- (void)adjustFontSize -{ - if (_minimumScaleFactor <= 0 || _minimumScaleFactor >= 1) { - return; - } - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { - NSString *str = textStorage.string; - NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - NSString *longestWordNeedingResize = @""; - for (NSString *word in words) { - if ([word length] > [longestWordNeedingResize length]) { - longestWordNeedingResize = word; - } - } - - if ([longestWordNeedingResize length] == 0) { - return; - } - - NSRange range = [str rangeOfString:longestWordNeedingResize]; - NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:range].mutableCopy; - CGSize defaultSize = [self sizeForAttributedString:attrString]; - - if (defaultSize.width > _constrainedSize.width) { - [attrString removeAttribute:NSParagraphStyleAttributeName range:NSMakeRange(0, [attrString length])]; - NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init]; - context.minimumScaleFactor = _minimumScaleFactor; - [attrString boundingRectWithSize:CGSizeMake(_constrainedSize.width, defaultSize.height) - options:NSStringDrawingUsesLineFragmentOrigin - context:context]; - - [self adjustFontSizeForAttributeString:attrString withScaleFactor:context.actualScaleFactor]; - - if ([self sizeForAttributedString:attrString].width <= _constrainedSize.width) { - [self adjustFontSizeForAttributeString:textStorage withScaleFactor:context.actualScaleFactor]; - NSLog(@"ASTextKitFontSizeAdjuster : adjusted \"%@\"to fontsize actualScaleFactor:%f", longestWordNeedingResize, context.actualScaleFactor); - } - } - }]; -} - -@end diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm new file mode 100644 index 0000000000..4f92c607aa --- /dev/null +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -0,0 +1,185 @@ +/* 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 "ASTextKitContext.h" +#import "ASTextKitFontSizeAdjuster.h" +#import "ASLayoutManager.h" + +#import + +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + +@implementation ASTextKitFontSizeAdjuster +{ + __weak ASTextKitContext *_context; + ASTextKitAttributes _attributes; + std::mutex _textKitMutex; +} + +- (instancetype)initWithContext:(ASTextKitContext *)context + constrainedSize:(CGSize)constrainedSize + textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; +{ + if (self = [super init]) { + _context = context; + _constrainedSize = constrainedSize; + _attributes = textComponentAttributes; + } + return self; +} + ++ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor +{ + [attrString beginEditing]; + + // scale all the attributes that will change the bounding box + [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + if (attrs[NSFontAttributeName] != nil) { + UIFont *font = attrs[NSFontAttributeName]; + font = [font fontWithSize:roundf(font.pointSize * scaleFactor)]; + [attrString removeAttribute:NSFontAttributeName range:range]; + [attrString addAttribute:NSFontAttributeName value:font range:range]; + } + + if (attrs[NSKernAttributeName] != nil) { + NSNumber *kerning = attrs[NSKernAttributeName]; + [attrString removeAttribute:NSKernAttributeName range:range]; + [attrString addAttribute:NSKernAttributeName value:@([kerning floatValue] * scaleFactor) range:range]; + } + + if (attrs[NSParagraphStyleAttributeName] != nil) { + NSMutableParagraphStyle *paragraphStyle = [attrs[NSParagraphStyleAttributeName] mutableCopy]; + paragraphStyle.lineSpacing = (paragraphStyle.lineSpacing * scaleFactor); + paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); + paragraphStyle.firstLineHeadIndent = (paragraphStyle.firstLineHeadIndent * scaleFactor); + paragraphStyle.headIndent = (paragraphStyle.headIndent * scaleFactor); + paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor); + paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor); + paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor); + paragraphStyle.lineHeightMultiple = (paragraphStyle.lineHeightMultiple * scaleFactor); + paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); + + [attrString removeAttribute:NSParagraphStyleAttributeName range:range]; + [attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; + } + + }]; + + [attrString endEditing]; +} + +- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString +{ + NSUInteger lineCount = 0; + + static std::mutex __static_mutex; + std::lock_guard l(__static_mutex); + + NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; + NSLayoutManager *layoutManager = _attributes.layoutManagerFactory ? _attributes.layoutManagerFactory() : [[ASLayoutManager alloc] init]; + layoutManager.usesFontLeading = NO; + [textStorage addLayoutManager:layoutManager]; + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize]; + + textContainer.lineFragmentPadding = 0; + textContainer.lineBreakMode = _attributes.lineBreakMode; + + // use 0 regardless of what is in the attributes so that we get an accurate line count + textContainer.maximumNumberOfLines = 0; + textContainer.exclusionPaths = _attributes.exclusionPaths; + [layoutManager addTextContainer:textContainer]; + + NSRange lineRange = { 0, 0 }; + while (NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]/* && lineCount <= _attributes.maximumNumberOfLines*/) { + [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; + lineCount++; + } + + return lineCount; +} + +- (CGFloat)scaleFactor +{ + if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) { + return 1.0; + } + + __block CGFloat adjustedScale = 1.0; + + NSArray *scaleFactors = _attributes.pointSizeScaleFactors; + [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + + // Check for two different situations (and correct for both) + // 1. The longest word in the string fits without being wrapped + // 2. The entire text fits in the given constrained size. + + NSString *str = textStorage.string; + NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *longestWordNeedingResize = @""; + for (NSString *word in words) { + if ([word length] > [longestWordNeedingResize length]) { + longestWordNeedingResize = word; + } + } + + NSUInteger scaleIndex = 0; + + // find the longest word and make sure it fits in the constrained width + if ([longestWordNeedingResize length] > 0) { + + NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize]; + NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy; + CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(FLT_MAX, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; + + // check if the longest word is larger than our constrained width + if (longestWordSize.width > _constrainedSize.width) { + + // we have a word that is too long. Loop through our scale factors until we fit + for (NSNumber *scaleFactor in scaleFactors) { + // even if we still don't fit, save this scaleFactor so more of the word will fit + adjustedScale = [scaleFactor floatValue]; + + // adjust here so we start at the proper place in our scale array if we have too many lines + scaleIndex++; + + if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { + // we fit! we are done + break; + } + } + } + } + + if (_attributes.maximumNumberOfLines > 0) { + // get the number of lines in our possibly scaled string + NSUInteger numberOfLines = [self lineCountForString:textStorage]; + if (numberOfLines > _attributes.maximumNumberOfLines) { + + for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) { + NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]]; + + + // save away this scale factor. Even if we don't fit completely we should still scale down + adjustedScale = [scaleFactors[index] floatValue]; + + if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) { + // we fit! we are done + break; + } + } + + } + } + + }]; + return adjustedScale; +} + +@end diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h index 75fc5e03a9..1889131f52 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.h +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.h @@ -55,6 +55,8 @@ @property (nonatomic, assign, readwrite) CGSize constrainedSize; +@property (nonatomic, assign, readonly) CGFloat currentScaleFactor; + #pragma mark - Drawing /* Draw the renderer's text content into the bounds provided. diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 05996c4de3..825ce2e26e 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -49,6 +49,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() _constrainedSize = constrainedSize; _attributes = attributes; _sizeIsCalculated = NO; + if ([attributes.pointSizeScaleFactors count] > 0) { + _currentScaleFactor = attributes.currentScaleFactor; + } } return self; } @@ -84,8 +87,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // We must inset the constrained size by the size of the shadower. CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; _fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context] - minimumScaleFactor:attributes.minimumScaleFactor - constrainedSize:shadowConstrainedSize]; + constrainedSize:shadowConstrainedSize + textKitAttributes:attributes]; } return _fontSizeAdjuster; } @@ -137,8 +140,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (void)_calculateSize { [self truncater]; - if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) { - [[self fontSizeAdjuster] adjustFontSize]; + if ([_attributes.pointSizeScaleFactors count] > 0) { + _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor]; } // Force glyph generation and layout, which may not have happened yet (and isn't triggered by @@ -156,8 +159,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect // to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect. boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size}); - - _calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size]; + CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size]; + _calculatedSize = CGSizeMake(boundingSize.width, boundingSize.height); + + if (_currentScaleFactor > 0.0 && _currentScaleFactor < 1.0) { + _calculatedSize.height = ceilf(_calculatedSize.height * _currentScaleFactor); + } } #pragma mark - Drawing @@ -176,11 +183,32 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds)); [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + + NSTextStorage *scaledTextStorage = nil; + BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0); + + if (isScaled) { + // if we are going to scale the text, swap out the non-scaled text for the scaled version. + NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor]; + scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString]; + + [textStorage removeLayoutManager:layoutManager]; + [scaledTextStorage addLayoutManager:layoutManager]; + } + LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer])); - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(0,0,textContainer.size.width, textContainer.size.height) inTextContainer:textContainer]; LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer])); + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; + + if (isScaled) { + // put the non-scaled version back + [scaledTextStorage removeLayoutManager:layoutManager]; + [textStorage addLayoutManager:layoutManager]; + } }]; UIGraphicsPopContext(); From 0e7fae1825d1df3404de64f1a8b868b5794548a8 Mon Sep 17 00:00:00 2001 From: rcancro Date: Fri, 12 Feb 2016 11:22:13 -0800 Subject: [PATCH 130/224] reverted some debug code --- AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index 4f92c607aa..f1be0df651 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -95,10 +95,8 @@ textContainer.exclusionPaths = _attributes.exclusionPaths; [layoutManager addTextContainer:textContainer]; - NSRange lineRange = { 0, 0 }; - while (NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]/* && lineCount <= _attributes.maximumNumberOfLines*/) { + for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) { [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; - lineCount++; } return lineCount; From d4ca8f4fd816077430bc08da0f3803132d4d9cbb Mon Sep 17 00:00:00 2001 From: rcancro Date: Tue, 16 Feb 2016 08:56:09 -0800 Subject: [PATCH 131/224] add ASTextKitFontAdjuster.mm to the build. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 285d1b7ad2..e57d0a3ff2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -286,12 +286,13 @@ 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; + 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; + 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; + 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; - A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; - A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; }; - A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; }; + A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Public, ); }; }; A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; @@ -715,10 +716,10 @@ 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutable.h; path = AsyncDisplayKit/Layout/ASStaticLayoutable.h; sourceTree = ""; }; 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = ""; }; 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; + 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = ""; }; - A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitFontSizeAdjuster.m; path = TextKit/ASTextKitFontSizeAdjuster.m; sourceTree = ""; }; A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = ""; }; AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; @@ -1206,7 +1207,7 @@ 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */, 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */, A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */, - A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */, + 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */, 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */, 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */, 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */, @@ -1470,6 +1471,7 @@ B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, + 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, @@ -1810,7 +1812,6 @@ 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */, 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */, - A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */, 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */, @@ -1824,6 +1825,7 @@ ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */, 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, + 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, @@ -1910,6 +1912,7 @@ 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, + 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, @@ -1945,7 +1948,6 @@ 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, - A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */, From 8f07197573a5ca2a192f75689b552ee28a54e068 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 16 Feb 2016 19:05:17 -0800 Subject: [PATCH 132/224] Don't need this :) --- AsyncDisplayKit/ASImageNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 2824a2e81f..87e6a02427 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -145,7 +145,7 @@ self.placeholderEnabled = placeholderColor != nil; } -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer; +- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds opaque:self.opaque From da953ac4214254b560a8f506487a4453c9f8abce Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Feb 2016 19:43:47 -0800 Subject: [PATCH 133/224] Provide a Synchronous Path for Loading Memory-Cached Images --- AsyncDisplayKit/ASMultiplexImageNode.mm | 2 +- .../Details/ASPINRemoteImageDownloader.m | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 77acf900e7..e407e2a47d 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -78,7 +78,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent __weak NSOperation *_phImageRequestOperation; // Networking. - ASDN::Mutex _downloadIdentifierLock; + ASDN::RecursiveMutex _downloadIdentifierLock; id _downloadIdentifier; //set on init only diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 256013739c..dc15fb98ec 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -10,6 +10,7 @@ #import "ASPINRemoteImageDownloader.h" #import "ASAssert.h" +#import "ASThread.h" #import #import @@ -32,16 +33,22 @@ callbackQueue:(dispatch_queue_t)callbackQueue completion:(void (^)(CGImageRef imageFromCache))completion { - //We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. - dispatch_async(callbackQueue, ^{ + // We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. + // If we're targeting the main queue and we're on the main thread, complete immediately. + if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { completion(nil); - }); + } else { + dispatch_async(callbackQueue, ^{ + completion(nil); + }); + } } - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL { - NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil]; - [[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] removeObjectForKey:key]; + PINRemoteImageManager *manager = [PINRemoteImageManager sharedImageManager]; + NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; + [[[manager cache] memoryCache] removeObjectForKey:key]; } - (nullable id)downloadImageWithURL:(NSURL *)URL @@ -50,9 +57,14 @@ completion:(void (^)(UIImage *image, NSError * error, id downloadIdentifier))completion { return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult *result) { - dispatch_async(callbackQueue, ^{ - completion(result.image, result.error, result.UUID); - }); + /// If we're targeting the main queue and we're on the main thread, complete immediately. + if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { + completion(result.image, result.error, result.UUID); + } else { + dispatch_async(callbackQueue, ^{ + completion(result.image, result.error, result.UUID); + }); + } }]; } From da3af0cadebdb8b81c36d7d3fcf837bae9ddb69c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Feb 2016 19:47:08 -0800 Subject: [PATCH 134/224] Remove null handling docs for nonnull argument --- AsyncDisplayKit/Details/ASImageProtocols.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index eb3690023f..46811e88c6 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -20,8 +20,7 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); /** @abstract Attempts to fetch an image with the given URL from the cache. @param URL The URL of the image to retrieve from the cache. - @param callbackQueue The queue to call `completion` on. If this value is nil, @{ref completion} will be invoked on the - main-queue. + @param callbackQueue The queue to call `completion` on. @param completion The block to be called when the cache has either hit or missed. @param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise. @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block From e1597f902ae0742f45a6f3351b03cd0e2ff54237 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 16 Feb 2016 22:19:27 -0800 Subject: [PATCH 135/224] Increase default value for leadingScreensForBatching to 2.0, as 1.0 is not enough for most network conditions / backend performance. --- AsyncDisplayKit/ASCollectionView.h | 2 +- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASTableView.h | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 6b4f8a38c6..f1475156e4 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -102,7 +102,7 @@ NS_ASSUME_NONNULL_BEGIN /** * The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called. * - * Defaults to one screenful. + * Defaults to two screenfuls. */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index eda88bf79f..b9f58ee5ff 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -180,7 +180,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _batchContext = [[ASBatchContext alloc] init]; - _leadingScreensForBatching = 1.0; + _leadingScreensForBatching = 2.0; _asyncDataFetchingEnabled = NO; _asyncDataSourceLocked = NO; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index b21d2ac94a..c2c50cab4a 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -106,7 +106,7 @@ NS_ASSUME_NONNULL_BEGIN /** * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. * - * Defaults to one screenful. + * Defaults to two screenfuls. */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 111e5afe18..477c6ff4a3 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -162,7 +162,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDataFetchingEnabled = NO; _asyncDataSourceLocked = NO; - _leadingScreensForBatching = 1.0; + _leadingScreensForBatching = 2.0; _batchContext = [[ASBatchContext alloc] init]; _automaticallyAdjustsContentOffset = NO; From 8d603bf5e666d0b6cb165319e9120c97be0ff2a2 Mon Sep 17 00:00:00 2001 From: Anh Nguyen Date: Wed, 17 Feb 2016 16:49:15 +0700 Subject: [PATCH 136/224] Check loadedImageIdentifer before sending it to delegate --- AsyncDisplayKit/ASMultiplexImageNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index e407e2a47d..5eb0abcfcb 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -219,7 +219,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent [self _setDownloadIdentifier:nil]; - if (_cacheSupportsClearing) { + if (_cacheSupportsClearing && self.loadedImageIdentifier != nil) { [_cache clearFetchedImageFromCacheWithURL:[_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]]; } From 070982a50ba5b1f6ab91fc633a122afee2a58efa Mon Sep 17 00:00:00 2001 From: rcancro Date: Wed, 17 Feb 2016 13:08:16 -0800 Subject: [PATCH 137/224] fixed tests??? (it did locally anyway) --- AsyncDisplayKit/ASTextNode.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index a6c17e5bf0..ca45d23330 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -258,9 +258,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // expensive, and can take some time, so we dispatch onto a bg queue to // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; + // before we remove the renderer, take its scale factor so we set it when a new renderer is created + _currentScaleFactor = renderer.currentScaleFactor; + ASPerformBlockOnBackgroundThread(^{ - // before we remove the renderer, take its scale factor so we set it when a new renderer is created - _currentScaleFactor = renderer.currentScaleFactor; renderer = nil; }); _renderer = nil; From 4a37b0882e308b2af68cb77f2a93387264ebc990 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 17 Feb 2016 22:21:46 -0800 Subject: [PATCH 138/224] [ASCollectionView] iOS 7-only issue where willDisplayCell: is not called, fixed for only that OS version. This includes some method moves to make sure related methods are in closer proximitiy. --- AsyncDisplayKit/ASCollectionView.mm | 98 +++++++++++--------- AsyncDisplayKit/Private/ASInternalHelpers.h | 2 + AsyncDisplayKit/Private/ASInternalHelpers.mm | 10 ++ 3 files changed, 67 insertions(+), 43 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b9f58ee5ff..088f8bac02 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -481,14 +481,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark Intercepted selectors. -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; - - ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - cell.node = node; - [_rangeController configureContentView:cell.contentView forCellNode:node]; - return cell; + _superIsPendingDataLoad = NO; + return [_dataController numberOfSections]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + return [_dataController numberOfRowsInSection:section]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath @@ -505,17 +506,59 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return view; } -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView + + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - _superIsPendingDataLoad = NO; - return [_dataController numberOfSections]; + _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; + + ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; + cell.node = node; + [_rangeController configureContentView:cell.contentView forCellNode:node]; + + if (ASRunningOnOS7()) { + // Even though UICV was introduced in iOS 6, and UITableView has always had the equivalent method, + // -willDisplayCell: was not introduced until iOS 8 for UICV. didEndDisplayingCell, however, is available. + [self collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; + } + + return cell; } -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { - return [_dataController numberOfRowsInSection:section]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; + + if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { + [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; + } + + ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; + if (cellNode.neverShowPlaceholders) { + [cellNode recursivelyEnsureDisplaySynchronously:YES]; + } } +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + + if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { + ASCellNode *node = ((_ASCollectionViewCell *)cell).node; + ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); + [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { + [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; + } +#pragma clang diagnostic pop +} + +#pragma mark - +#pragma mark Scroll Direction. + - (ASScrollDirection)scrollDirection { CGPoint scrollVelocity; @@ -577,37 +620,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return scrollableDirection; } -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; - - if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { - [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; - } - - ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; - if (cellNode.neverShowPlaceholders) { - [cellNode recursivelyEnsureDisplaySynchronously:YES]; - } -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; - - if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { - ASCellNode *node = ((_ASCollectionViewCell *)cell).node; - ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); - [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { - [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop -} - - (void)layoutSubviews { if (_zeroContentInsets) { diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 1f173bd951..11709e8373 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -28,6 +28,8 @@ CGFloat ASCeilPixelValue(CGFloat f); CGFloat ASRoundPixelValue(CGFloat f); +BOOL ASRunningOnOS7(); + ASDISPLAYNODE_EXTERN_C_END /** diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 557d52119c..51e8d2685b 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -93,6 +93,16 @@ CGFloat ASRoundPixelValue(CGFloat f) return roundf(f * ASScreenScale()) / ASScreenScale(); } +BOOL ASRunningOnOS7() +{ + static BOOL isOS7 = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + isOS7 = ([[UIDevice currentDevice].systemVersion floatValue] < 8.0); + }); + return isOS7; +} + @implementation NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath From 0cf972d7ac049b063cb9464d67865e123e914363 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 17 Feb 2016 22:28:12 -0800 Subject: [PATCH 139/224] [ASVideoNode] Remove beta warning in preparation for 1.9.7 launch. --- AsyncDisplayKit/ASVideoNode.h | 6 +++--- AsyncDisplayKit/ASVideoNode.mm | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 515dc20f4b..ebc9957c0c 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -10,9 +10,9 @@ @protocol ASVideoNodeDelegate; -// If you need ASVideoNode, please use AsyncDisplayKit master until this comment is removed. -// As of 1.9.6, ASVideoNode accidentally triggers creating the AVPlayerLayer even before playing -// the video. Using a lot of them intended to show static frame placeholders will be slow. +// This is a relatively new component of AsyncDisplayKit. It has many useful features, but +// there is room for further expansion and optimization. Please report any issues or requests +// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues @interface ASVideoNode : ASControlNode @property (atomic, strong, readwrite) AVAsset *asset; diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index e2efb5a80e..9df53ab874 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -46,11 +46,7 @@ if (!(self = [super init])) { return nil; } - -#if DEBUG - NSLog(@"*** Warning: ASVideoNode is a new component - the 1.9.6 version may cause performance hiccups."); -#endif - + _previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); self.playButton = [[ASDefaultPlayButton alloc] init]; From 19cc368d159992408a2a20fb7326924480292be9 Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Wed, 17 Feb 2016 13:19:00 -0800 Subject: [PATCH 140/224] In addition to allocating nodes in the background, perform that operation concurrently in ASDataController --- AsyncDisplayKit/ASCollectionView.mm | 4 ++- AsyncDisplayKit/ASTableView.mm | 6 +++-- AsyncDisplayKit/Details/ASDataController.mm | 27 ++++++++++++++++----- AsyncDisplayKitTests/ASDisplayNodeTests.m | 1 + 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b9f58ee5ff..3d80ea8107 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -643,7 +643,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) ); - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + if (targetContentOffset != NULL) { + [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 477c6ff4a3..99009d8eb9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -637,8 +637,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) ); - - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + + if (targetContentOffset != NULL) { + [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index a792eedc6d..32d62adac2 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -162,24 +162,37 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } NSUInteger nodeCount = nodes.count; - NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; + NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); + for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); + __block NSArray *subarray; + // Allocate nodes concurrently. dispatch_block_t allocationBlock = ^{ - for (NSUInteger k = j; k < j + batchCount; k++) { + __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(batchCount, sizeof(ASCellNode *)); + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_apply(batchCount, queue, ^(size_t i) { + unsigned long k = j + i; ASCellNodeBlock cellBlock = nodes[k]; ASCellNode *node = cellBlock(); ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); - [allocatedNodes addObject:node]; + allocatedNodeBuffer[i] = node; if (!node.isNodeLoaded) { nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; } + }); + subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount]; + + // Nil out buffer indexes to allow arc to free the stored cells. + for (int i = 0; i < batchCount; i++) { + allocatedNodeBuffer[i] = nil; } + free(allocatedNodeBuffer); }; - + if (ASDisplayNodeThreadIsMain()) { dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -187,14 +200,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - [self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; } else { allocationBlock(); [_mainSerialQueue performBlockOnMainThread:^{ - [self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + [self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; }]; } + [allocatedNodes addObjectsFromArray:subarray]; + dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSUInteger k = j; k < j + batchCount; k++) { ASCellNode *node = allocatedNodes[k]; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 9f34114231..455591cd1b 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -166,6 +166,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ } - (BOOL)resignFirstResponder { + [super resignFirstResponder]; if (self.isFirstResponder) { self.isFirstResponder = NO; return YES; From 76708c47cf921603610bc937dfe59ea59e5659f3 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 09:54:48 -0800 Subject: [PATCH 141/224] [ASCollectionView] Always honor sectionInset in both dimensions --- AsyncDisplayKit/ASCollectionView.mm | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 2a807a530c..b69e685ed7 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -758,19 +758,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (_asyncDelegateImplementsInsetSection) { sectionInset = [(id)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section]; } - - if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { - constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right); - //ignore insets for FLT_MAX so FLT_MAX can be compared against - if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) { - constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right); - } - } else { - constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom); - //ignore insets for FLT_MAX so FLT_MAX can be compared against - if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) { - constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom); - } + + constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom); + constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right); + //ignore insets for FLT_MAX so FLT_MAX can be compared against + if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) { + constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right); + } + if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) { + constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom); } return constrainedSize; From eeb81a54de62aaea917b9a17e419f1338c593c41 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 18 Feb 2016 13:59:20 -0800 Subject: [PATCH 142/224] [ASDisplayNode+Beta] Expose helper functions, ASPerformBlockOnMainThread & ASPerformBlockOnBackgroundThread. --- AsyncDisplayKit/ASDisplayNode+Beta.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index bad81b740a..aa7df08583 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,6 +8,11 @@ #import "ASContextTransitioning.h" +ASDISPLAYNODE_EXTERN_C_BEGIN +void ASPerformBlockOnMainThread(void (^block)()); +void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT +ASDISPLAYNODE_EXTERN_C_END + @interface ASDisplayNode (Beta) + (BOOL)usesImplicitHierarchyManagement; From 40791dd8591fb63a300bffba1e9d27d6cb17a580 Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Wed, 17 Feb 2016 14:52:33 -0800 Subject: [PATCH 143/224] optimize reload data, reload sections, & move nodes performance & logic --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.mm | 105 ++++- AsyncDisplayKit/ASTableView.mm | 83 +++- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 81 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 386 +++++++----------- AsyncDisplayKit/Details/ASRangeController.h | 53 +++ AsyncDisplayKit/Details/ASRangeController.mm | 28 +- AsyncDisplayKitTests/ASTableViewTests.m | 2 +- 11 files changed, 479 insertions(+), 347 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 285d1b7ad2..a332403bb3 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -646,7 +646,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b9f58ee5ff..d57bf44ddf 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController reloadDataImmediately]; [super reloadData]; } @@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -941,6 +941,46 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -961,6 +1001,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadSections:indexSet]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadSections:indexSet]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -981,6 +1041,43 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } +} + +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_batchUpdateBlocks addObject:^{ + [super reloadData]; + }]; + } else { + [UIView performWithoutAnimation:^{ + [super reloadData]; + }]; + } +} + #pragma mark - ASCellNodeDelegate - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 477c6ff4a3..d91d0241f0 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASPerformBlockOnMainThread(^{ - [super reloadData]; - }); - [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; - [super reloadData]; + [_dataController reloadDataImmediately]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } #pragma mark - @@ -834,6 +830,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); + + if (_automaticallyAdjustsContentOffset) { + [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -850,6 +876,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadSections:%@", indexSet); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView moveSection:%@", indexSet); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super moveSection:fromIndex toSection:toIndex]; +} + + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -865,6 +921,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadData"); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super reloadData]; +} + #pragma mark - ASDataControllerDelegate - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 666ea86ce7..bdc0c4993c 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; + [super moveSection:section toSection:newSection]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..979acd30e3 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,37 +49,27 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - - // Insert each section + // Insert sections NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + self.editingNode[kind] = sections; + + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -91,9 +81,6 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -104,23 +91,22 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } @@ -132,40 +118,31 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; + }]; + // reinsert the elements + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - }]; - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self moveSection:section ofKind:kind toSection:newSection]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..54f1fc259a 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,16 +14,7 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying - -/** - * Provides a collection of index paths for nodes of the given kind that are currently in the editing store - */ -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; - -/** - * Read-only access to the underlying editing nodes of the given kind - */ -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; +@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; /** * Read only access to the underlying completed nodes of the given kind @@ -35,7 +26,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -53,24 +44,34 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. + * Inserts the given nodes of the specified kind into the backing store. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. + * Deletes the given nodes of the specified kind in the backing store. */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Inserts the given sections of the specified kind in the backing store. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; /** - * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Deletes the given sections of the specified kind in the backing store. */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; + +/** + * Moves the given section of the specified kind in the backing store. + */ +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection; + +/** + * Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished. + */ +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6648275d9b..1766b7dfc0 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of elements. + */ +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of elements. + */ +- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of sections. + */ +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of sections. + */ +- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + Called for reload data. + */ +- (void)dataControllerDidReloadData:(ASDataController *)dataController; + @end /** @@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; -/** @name Initial loading - * - * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, - * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. - */ - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** @name Data Updating */ - (void)beginUpdates; @@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; +- (void)reloadDataWithCompletion:(void (^)())completion; -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)reloadDataImmediately; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index a792eedc6d..e9c93e907b 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; + BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; + BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + BOOL _delegateDidReloadSections; + BOOL _delegateDidMoveSection; + BOOL _delegateDidReloadData; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -92,8 +96,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; + _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)]; + _delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; + _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - - // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; - } + [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -144,21 +150,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); } -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (!nodes.count) { - return; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + return; } NSUInteger nodeCount = nodes.count; @@ -223,178 +221,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) return; + LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; } -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) { - return; + return @[]; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); NSMutableArray *editingNodes = _editingNodes[kind]; + NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - - [_mainSerialQueue performBlockOnMainThread:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + return deletedNodes; } -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{ if (indexSet.count == 0) return; + LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; } -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet { if (indexSet.count == 0) return; + + LOG(@"deleteSectionsOfKind:%@", kind); [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; +} + +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection +{ + NSArray *movedSection = _editingNodes[kind][section]; + [_editingNodes[kind] removeObjectAtIndex:section]; + [_editingNodes[kind] insertObject:movedSection atIndex:newSection]; +} + +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock +{ + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]); + [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + _completedNodes[kind] = completedNodes; if (completionBlock) { - completionBlock(indexSet); + completionBlock(); } }]; } -#pragma mark - Internal Data Querying + Editing +#pragma mark - Reload (External API) -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataWithCompletion:(void (^)())completion { - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:NO completion:completion]; } -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataImmediately { - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:YES completion:nil]; } -/** - * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indicies from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - [self accessDataSourceWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; - - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; - - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; - } - } - - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; -} - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion +- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -402,39 +306,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; void (^transactionBlock)() = ^{ LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - + [self willReloadData]; - - // Insert each section + + // Insert sections NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - - [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; + _editingNodes[ASDataControllerRowNodeKind] = sections; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadData) { + [_delegate dataControllerDidReloadData:self]; + } + if (completion) { + completion(); + } + }]; + }]; }; if (synchronously) { @@ -533,15 +433,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_editingTransactionQueue addOperationWithBlock:^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataControllerBeginUpdates:self]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -552,15 +445,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - - [_editingTransactionQueue addOperationWithBlock:^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; + + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; } } @@ -592,9 +479,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -607,8 +494,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; + + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -626,10 +519,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -643,29 +538,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; + }]; - // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadSections) + [_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -675,24 +573,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; - - // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveSection) { + [_delegate dataController:self didMoveSection:section toSection:newSection]; + } + }]; }]; }]; } @@ -760,7 +648,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -780,7 +673,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -795,20 +692,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadNodes) + [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -854,7 +756,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -864,26 +766,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; // Don't re-calculate size for moving - NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveNode) { + [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + }]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; -} - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; +- (NSMutableDictionary *)editingNode{ + return _editingNodes; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -950,11 +849,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; + return _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d5288f40e2..c3e6f31551 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for nodes reload. + * + * @param rangeController Sender. + * + * @param nodes Inserted nodes. + * + * @param indexPaths Index path of reloaded nodes. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + * Called for movement of node. + * + * @param rangeController Sender. + * + * @param fromIndexPath Index path of moved node before the movement. + * + * @param toIndexPath Index path of moved node after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** * Called for section insertion. * @@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for section reload. + * + * @param rangeController Sender. + * + * @param indexSet Index set of reloaded sections. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + /** * Called for section deletion. * @@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for movement of section. + * + * @param rangeController Sender. + * + * @param fromIndex Index of moved section before the movement. + * + * @param toIndex Index of moved section after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + * Called for reload data. + * + * @param rangeController Sender. + */ +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController; + @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b8d7d8f89..c13ef1cdf6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,15 +361,30 @@ }); } -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{ + ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASPerformBlockOnMainThread(^{ @@ -378,4 +393,11 @@ }); } -@end \ No newline at end of file +- (void)dataControllerDidReloadData:(ASDataController *)dataController{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeControllerDidReloadData:self]; + }); +} + +@end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 2949a16e2f..9de6b2d11f 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -15,7 +15,7 @@ #define NumberOfSections 10 #define NumberOfRowsPerSection 20 -#define NumberOfReloadIterations 50 +#define NumberOfReloadIterations 500 @interface ASTestDataController : ASChangeSetDataController @property (atomic) int numberOfAllNodesRelayouts; From cefc985171c982e9fed29adad90e8545c01c74f8 Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Thu, 18 Feb 2016 16:28:03 -0800 Subject: [PATCH 144/224] reset tests --- AsyncDisplayKitTests/ASTableViewTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 9de6b2d11f..2949a16e2f 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -15,7 +15,7 @@ #define NumberOfSections 10 #define NumberOfRowsPerSection 20 -#define NumberOfReloadIterations 500 +#define NumberOfReloadIterations 50 @interface ASTestDataController : ASChangeSetDataController @property (atomic) int numberOfAllNodesRelayouts; From 2adc30440f0da642460e11c231297b8c5dc001e7 Mon Sep 17 00:00:00 2001 From: rcancro Date: Thu, 18 Feb 2016 16:41:58 -0800 Subject: [PATCH 145/224] Exposing currentScaleFactor --- AsyncDisplayKit/ASTextNode+Beta.h | 5 +++++ AsyncDisplayKit/ASTextNode.mm | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 38059aa7c3..b4d3b88982 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -15,4 +15,9 @@ */ @property (nonatomic, copy) NSArray *pointSizeScaleFactors; +/** + @abstract The currently applied scale factor, or 0 if the text node is not being scaled. + */ +@property (nonatomic, assign, readonly) CGFloat currentScaleFactor; + @end \ No newline at end of file diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index ca45d23330..202a5bb2ed 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -79,7 +79,6 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation CGSize _constrainedSize; ASTextKitRenderer *_renderer; - CGFloat _currentScaleFactor; UILongPressGestureRecognizer *_longPressGestureRecognizer; } @@ -245,7 +244,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, .pointSizeScaleFactors = _pointSizeScaleFactors, - .currentScaleFactor = _currentScaleFactor, + .currentScaleFactor = self.currentScaleFactor, }; } From 75eab1db07382d33598b082b27e65833dd65cc08 Mon Sep 17 00:00:00 2001 From: Max Gu Date: Thu, 18 Feb 2016 18:00:37 -0800 Subject: [PATCH 146/224] Adding scroll visibility --- AsyncDisplayKit/ASCellNode.h | 5 +++++ AsyncDisplayKit/ASCellNode.m | 6 ++++++ AsyncDisplayKit/ASCollectionView.mm | 16 +++++++++++++++- AsyncDisplayKit/Details/ASDelegateProxy.m | 3 +++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index d1e575383e..669c6757f5 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -16,6 +16,7 @@ typedef NSUInteger ASCellNodeAnimation; @protocol ASCellNodeLayoutDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView cellFrameInScrollView:(CGRect)cellFrame; /** * Notifies the delegate that the specified cell node has done a relayout. * The notification is done on main thread. @@ -24,6 +25,8 @@ typedef NSUInteger ASCellNodeAnimation; * @param sizeChanged `YES` if the node's `calculatedSize` changed during the relayout, `NO` otherwise. */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; + + @end /** @@ -75,6 +78,7 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, weak) id layoutDelegate; +@property (nonatomic, assign) BOOL shouldObserveVisibility; /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. @@ -107,6 +111,7 @@ typedef NSUInteger ASCellNodeAnimation; */ - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; +- (void)updateScrollSituationWithScrollVIew:(UIScrollView *)scrollView; @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index df7040d92e..840c5edea3 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -134,6 +134,12 @@ [(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event]; } +- (void)updateScrollSituationWithScrollVIew:(UIScrollView *)scrollView +{ + // TODO(Max): Fix the cellFrame here + [self.layoutDelegate scrollViewDidScroll:scrollView cellFrameInScrollView:CGRectZero]; +} + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b9f58ee5ff..ccfaa73115 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -67,7 +67,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; - + NSMutableArray *_cellsForVisibilityUpdates; id _layoutFacilitator; BOOL _performingBatchUpdates; @@ -212,6 +212,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _registeredSupplementaryKinds = [NSMutableSet set]; + _cellsForVisibilityUpdates = [[NSMutableArray alloc] init]; self.backgroundColor = [UIColor whiteColor]; [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kCellReuseIdentifier]; @@ -589,6 +590,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } + [_cellsForVisibilityUpdates addObject:cell]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath @@ -600,6 +602,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; } + [_cellsForVisibilityUpdates removeObject:cell]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { @@ -650,6 +653,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ + for (ASCellNode *node in _cellsForVisibilityUpdates) { + if (node.shouldObserveVisibility) { + [node updateScrollSituationWithScrollVIew:scrollView]; + } + } + if ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} + - (BOOL)shouldBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 2af0e7c7d5..baa65a65c7 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -56,6 +56,9 @@ // used for batch fetching API selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + // used for ASCellNode visibility + selector == @selector(scrollViewDidScroll:) || + // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) selector == @selector(collectionView:canMoveItemAtIndexPath:) || selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) || From 32aa333c21bea6eaf8c0ac55c7f12d8b1d598a58 Mon Sep 17 00:00:00 2001 From: appleguy Date: Thu, 18 Feb 2016 19:50:06 -0800 Subject: [PATCH 147/224] Revert "[ASCollectionView / ASTableView] Optimize reloadData and reloadSection: methods." --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.mm | 105 +---- AsyncDisplayKit/ASTableView.mm | 83 +--- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 79 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 400 +++++++++++------- AsyncDisplayKit/Details/ASRangeController.h | 53 --- AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 352 insertions(+), 484 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ac70348372..e57d0a3ff2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -647,7 +647,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 07484f3622..b69e685ed7 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithCompletion:completion]; + [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; } - (void)reloadData @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; [super reloadData]; } @@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -951,46 +951,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super reloadItemsAtIndexPaths:indexPaths]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - [UIView performWithoutAnimation:^{ - [super reloadItemsAtIndexPaths:indexPaths]; - }]; - } -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO]; - [UIView performWithoutAnimation:^{ - [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; - }]; - } -} - - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -1011,26 +971,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super reloadSections:indexSet]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; - [UIView performWithoutAnimation:^{ - [super reloadSections:indexSet]; - }]; - } -} - - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -1051,43 +991,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super moveSection:fromIndex toSection:toIndex]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO]; - [UIView performWithoutAnimation:^{ - [super moveSection:fromIndex toSection:toIndex]; - }]; - } -} - -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super reloadData]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super reloadData]; - }]; - } -} - #pragma mark - ASCellNodeDelegate - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bbcfb42bba..99009d8eb9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -303,7 +303,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - [_dataController reloadDataWithCompletion:completion]; + ASPerformBlockOnMainThread(^{ + [super reloadData]; + }); + [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; } - (void)reloadData @@ -314,7 +317,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; + [super reloadData]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -429,7 +433,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -453,7 +457,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; } #pragma mark - @@ -832,36 +836,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }); - - if (_automaticallyAdjustsContentOffset) { - [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; - } -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath -{ - ASDisplayNodeAssertMainThread(); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; -} - - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -878,36 +852,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadSections:%@", indexSet); - - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - [super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }); -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView moveSection:%@", indexSet); - - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [super moveSection:fromIndex toSection:toIndex]; -} - - - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -923,17 +867,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadData"); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [super reloadData]; -} - #pragma mark - ASDataControllerDelegate - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index bdc0c4993c..666ea86ce7 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection]; + [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 979acd30e3..2408a57e1f 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,27 +49,37 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Insert sections + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + + NSArray *editingNodes = [self editingNodesOfKind:kind]; + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; + + // Insert each section NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - self.editingNode[kind] = sections; - - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; + + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -81,6 +91,9 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -91,22 +104,23 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - [self deleteSectionsOfKind:kind atIndexSet:sections]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; } } @@ -118,31 +132,40 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - [self moveSection:section ofKind:kind toSection:newSection]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + + // update the section of indexpaths + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + }]; + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 54f1fc259a..32d787910e 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,7 +14,16 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying -@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; + +/** + * Provides a collection of index paths for nodes of the given kind that are currently in the editing store + */ +- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; + +/** + * Read-only access to the underlying editing nodes of the given kind + */ +- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; /** * Read only access to the underlying completed nodes of the given kind @@ -26,7 +35,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -44,34 +53,24 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store. + * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Deletes the given nodes of the specified kind in the backing store. + * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. */ -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Inserts the given sections of the specified kind in the backing store. + * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; /** - * Deletes the given sections of the specified kind in the backing store. + * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; - -/** - * Moves the given section of the specified kind in the backing store. - */ -- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection; - -/** - * Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished. - */ -- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 1766b7dfc0..6648275d9b 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,41 +91,16 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - Called for reload of elements. - */ -- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for movement of elements. - */ -- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; - /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - Called for reload of sections. - */ -- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for movement of sections. - */ -- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; - -/** - Called for reload data. - */ -- (void)dataControllerDidReloadData:(ASDataController *)dataController; - @end /** @@ -162,6 +137,14 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; +/** @name Initial loading + * + * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, + * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. + */ + +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + /** @name Data Updating */ - (void)beginUpdates; @@ -176,7 +159,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -192,11 +175,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadDataWithCompletion:(void (^)())completion; +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; -- (void)reloadDataImmediately; +- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7a7d28e446..32d62adac2 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,6 +30,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { + NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -41,14 +42,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; - BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; - BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; - BOOL _delegateDidReloadSections; - BOOL _delegateDidMoveSection; - BOOL _delegateDidReloadData; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -96,13 +92,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)]; - _delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; - _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; - _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -119,14 +110,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + + // Processing in batches + for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { + NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); + NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; + NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; + [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; + } } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -150,13 +144,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); } +/** + * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. + */ +- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + // Insert finished nodes into data storage + [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; +} + - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (!nodes.count) { - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - return; + return; } NSUInteger nodeCount = nodes.count; @@ -236,84 +238,178 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (indexPaths.count == 0) return; - LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); + + [_mainSerialQueue performBlockOnMainThread:^{ + _completedNodes[kind] = completedNodes; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (indexPaths.count == 0) { - return @[]; + return; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; - NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - return deletedNodes; + + [_mainSerialQueue performBlockOnMainThread:^{ + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock +{ if (indexSet.count == 0) return; - LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; -} - -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet -{ - if (indexSet.count == 0) - return; - - LOG(@"deleteSectionsOfKind:%@", kind); - [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; -} - -- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection -{ - NSArray *movedSection = _editingNodes[kind][section]; - [_editingNodes[kind] removeObjectAtIndex:section]; - [_editingNodes[kind] insertObject:movedSection atIndex:newSection]; -} - -- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock -{ - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]); - + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); + [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; + [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { - completionBlock(); + completionBlock(sections, indexSet); } }]; } -#pragma mark - Reload (External API) - -- (void)reloadDataWithCompletion:(void (^)())completion +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock { - [self _reloadDataSynchronously:NO completion:completion]; + if (indexSet.count == 0) + return; + [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + if (completionBlock) { + completionBlock(indexSet); + } + }]; } -- (void)reloadDataImmediately +#pragma mark - Internal Data Querying + Editing + +/** + * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. + * + * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy + * of the editing nodes. The delegate is invoked on the main thread. + */ +- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self _reloadDataSynchronously:YES completion:nil]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; } -- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion +/** + * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. + * + * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. + * Once the backing stores are consistent, the delegate is invoked on the main thread. + */ +- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; +} + +/** + * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. + * + * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted + * in the completed store on the main thread. The delegate is invoked on the main thread. + */ +- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; + }]; +} + +/** + * Removes sections at the given indicies from the backing store and notifies the delegate. + * + * @discussion Section array are first removed from the editing store, then the associated section in the completed + * store is removed on the main thread. The delegate is invoked on the main thread. + */ +- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }]; +} + +#pragma mark - Initial Load & Full Reload (External API) + +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [self accessDataSourceWithBlock:^{ + NSMutableArray *indexPaths = [NSMutableArray array]; + NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; + + // insert sections + [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; + + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; + + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; + } + } + + // insert elements + [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; +} + +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion +{ + [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; +} + +- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; +} + +- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -321,35 +417,39 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; void (^transactionBlock)() = ^{ LOG(@"Edit Transaction - reloadData"); - + + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + [self willReloadData]; - - // Insert sections + + // Insert each section NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - _editingNodes[ASDataControllerRowNodeKind] = sections; + + [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadData) { - [_delegate dataControllerDidReloadData:self]; - } - if (completion) { - completion(); - } - }]; - }]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + if (completion) { + dispatch_async(dispatch_get_main_queue(), completion); + } }; if (synchronously) { @@ -448,8 +548,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // Deep copy _completedNodes to _externalCompletedNodes. + // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. + _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); + + LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); + [_delegate dataControllerBeginUpdates:self]; + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -460,9 +567,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // Now that the transaction is done, _completedNodes can be accessed externally again. + _externalCompletedNodes = nil; + + LOG(@"endUpdatesWithCompletion - calling delegate end"); + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + }]; }]; } } @@ -494,9 +607,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -509,14 +622,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; - - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; - }]; + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -534,12 +641,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; }]; }]; } @@ -553,32 +658,29 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadSections) - [_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; - }]; + // reinsert the elements + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -588,14 +690,24 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; + + // remove elements LOG(@"Edit Transaction - moveSection"); - [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveSection) { - [_delegate dataController:self didMoveSection:section toSection:newSection]; - } - }]; + + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + // update the section of indexpaths + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSIndexPath *indexPath in indexPaths) { + [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + } + + // Don't re-calculate size for moving + [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; } @@ -663,12 +775,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -688,11 +795,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; }]; }]; } @@ -707,25 +810,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; - [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadNodes) - [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -771,7 +869,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -781,23 +879,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; + NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // Don't re-calculate size for moving - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveNode) { - [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; - } - }]; + NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSMutableDictionary *)editingNode{ - return _editingNodes; +- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; +} + +- (NSMutableArray *)editingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -864,10 +965,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } +/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _completedNodes[ASDataControllerRowNodeKind]; + return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index c3e6f31551..d5288f40e2 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,30 +163,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for nodes reload. - * - * @param rangeController Sender. - * - * @param nodes Inserted nodes. - * - * @param indexPaths Index path of reloaded nodes. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Called for movement of node. - * - * @param rangeController Sender. - * - * @param fromIndexPath Index path of moved node before the movement. - * - * @param toIndexPath Index path of moved node after the movement. - */ -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; - /** * Called for section insertion. * @@ -198,17 +174,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for section reload. - * - * @param rangeController Sender. - * - * @param indexSet Index set of reloaded sections. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** * Called for section deletion. * @@ -220,24 +185,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for movement of section. - * - * @param rangeController Sender. - * - * @param fromIndex Index of moved section before the movement. - * - * @param toIndex Index of moved section after the movement. - */ -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; - -/** - * Called for reload data. - * - * @param rangeController Sender. - */ -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController; - @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index c13ef1cdf6..1b8d7d8f89 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,30 +361,15 @@ }); } -- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{ - ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASPerformBlockOnMainThread(^{ @@ -393,11 +378,4 @@ }); } -- (void)dataControllerDidReloadData:(ASDataController *)dataController{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeControllerDidReloadData:self]; - }); -} - -@end +@end \ No newline at end of file From a62de38670e2f8d806fa6290d98bc1f12875348f Mon Sep 17 00:00:00 2001 From: rcancro Date: Thu, 18 Feb 2016 20:01:23 -0800 Subject: [PATCH 148/224] currentScaleFactor management --- AsyncDisplayKit/ASTextNode.mm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 202a5bb2ed..3c0d7797b0 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -257,8 +257,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // expensive, and can take some time, so we dispatch onto a bg queue to // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; - // before we remove the renderer, take its scale factor so we set it when a new renderer is created - _currentScaleFactor = renderer.currentScaleFactor; ASPerformBlockOnBackgroundThread(^{ renderer = nil; @@ -340,7 +338,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self setNeedsDisplay]; }); - return [[self _renderer] size]; + CGSize size = [[self _renderer] size]; + // the renderer computes the current scale factor during sizing, so let's grab it here + _currentScaleFactor = _renderer.currentScaleFactor; + return size; } #pragma mark - Modifying User Text @@ -381,6 +382,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } }); + // reset the scale factor if we get a new string. + _currentScaleFactor = 0; if (attributedString.length > 0) { CGFloat screenScale = ASScreenScale(); self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; From 58a3ba5e8f2918cda68521048c6c3f4f6077f4dd Mon Sep 17 00:00:00 2001 From: appleguy Date: Thu, 18 Feb 2016 23:06:14 -0800 Subject: [PATCH 149/224] Revert "Revert "[ASCollectionView / ASTableView] Optimize reloadData and reloadSection: methods."" --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.mm | 105 ++++- AsyncDisplayKit/ASTableView.mm | 83 +++- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 81 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 386 +++++++----------- AsyncDisplayKit/Details/ASRangeController.h | 53 +++ AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 478 insertions(+), 346 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e57d0a3ff2..ac70348372 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -647,7 +647,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b69e685ed7..07484f3622 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController reloadDataImmediately]; [super reloadData]; } @@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -951,6 +951,46 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -971,6 +1011,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadSections:indexSet]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadSections:indexSet]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -991,6 +1051,43 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } +} + +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_batchUpdateBlocks addObject:^{ + [super reloadData]; + }]; + } else { + [UIView performWithoutAnimation:^{ + [super reloadData]; + }]; + } +} + #pragma mark - ASCellNodeDelegate - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 99009d8eb9..bbcfb42bba 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASPerformBlockOnMainThread(^{ - [super reloadData]; - }); - [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; - [super reloadData]; + [_dataController reloadDataImmediately]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } #pragma mark - @@ -836,6 +832,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); + + if (_automaticallyAdjustsContentOffset) { + [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -852,6 +878,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadSections:%@", indexSet); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView moveSection:%@", indexSet); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super moveSection:fromIndex toSection:toIndex]; +} + + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -867,6 +923,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadData"); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super reloadData]; +} + #pragma mark - ASDataControllerDelegate - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 666ea86ce7..bdc0c4993c 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; + [super moveSection:section toSection:newSection]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..979acd30e3 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,37 +49,27 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - - // Insert each section + // Insert sections NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + self.editingNode[kind] = sections; + + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -91,9 +81,6 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -104,23 +91,22 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } @@ -132,40 +118,31 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; + }]; + // reinsert the elements + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - }]; - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self moveSection:section ofKind:kind toSection:newSection]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..54f1fc259a 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,16 +14,7 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying - -/** - * Provides a collection of index paths for nodes of the given kind that are currently in the editing store - */ -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; - -/** - * Read-only access to the underlying editing nodes of the given kind - */ -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; +@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; /** * Read only access to the underlying completed nodes of the given kind @@ -35,7 +26,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -53,24 +44,34 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. + * Inserts the given nodes of the specified kind into the backing store. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. + * Deletes the given nodes of the specified kind in the backing store. */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Inserts the given sections of the specified kind in the backing store. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; /** - * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Deletes the given sections of the specified kind in the backing store. */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; + +/** + * Moves the given section of the specified kind in the backing store. + */ +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection; + +/** + * Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished. + */ +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6648275d9b..1766b7dfc0 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of elements. + */ +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of elements. + */ +- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of sections. + */ +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of sections. + */ +- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + Called for reload data. + */ +- (void)dataControllerDidReloadData:(ASDataController *)dataController; + @end /** @@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; -/** @name Initial loading - * - * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, - * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. - */ - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** @name Data Updating */ - (void)beginUpdates; @@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; +- (void)reloadDataWithCompletion:(void (^)())completion; -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)reloadDataImmediately; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 32d62adac2..7a7d28e446 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; + BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; + BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + BOOL _delegateDidReloadSections; + BOOL _delegateDidMoveSection; + BOOL _delegateDidReloadData; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -92,8 +96,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; + _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)]; + _delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; + _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - - // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; - } + [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -144,21 +150,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); } -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (!nodes.count) { - return; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + return; } NSUInteger nodeCount = nodes.count; @@ -238,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) return; + LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; } -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) { - return; + return @[]; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); NSMutableArray *editingNodes = _editingNodes[kind]; + NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - - [_mainSerialQueue performBlockOnMainThread:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + return deletedNodes; } -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{ if (indexSet.count == 0) return; + LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; } -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet { if (indexSet.count == 0) return; + + LOG(@"deleteSectionsOfKind:%@", kind); [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; +} + +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection +{ + NSArray *movedSection = _editingNodes[kind][section]; + [_editingNodes[kind] removeObjectAtIndex:section]; + [_editingNodes[kind] insertObject:movedSection atIndex:newSection]; +} + +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock +{ + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]); + [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + _completedNodes[kind] = completedNodes; if (completionBlock) { - completionBlock(indexSet); + completionBlock(); } }]; } -#pragma mark - Internal Data Querying + Editing +#pragma mark - Reload (External API) -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataWithCompletion:(void (^)())completion { - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:NO completion:completion]; } -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataImmediately { - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:YES completion:nil]; } -/** - * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indicies from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - [self accessDataSourceWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; - - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; - - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; - } - } - - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; -} - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion +- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -417,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; void (^transactionBlock)() = ^{ LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - + [self willReloadData]; - - // Insert each section + + // Insert sections NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - - [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; + _editingNodes[ASDataControllerRowNodeKind] = sections; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadData) { + [_delegate dataControllerDidReloadData:self]; + } + if (completion) { + completion(); + } + }]; + }]; }; if (synchronously) { @@ -548,15 +448,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_editingTransactionQueue addOperationWithBlock:^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataControllerBeginUpdates:self]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -567,15 +460,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - - [_editingTransactionQueue addOperationWithBlock:^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; + + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; } } @@ -607,9 +494,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -622,8 +509,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; + + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -641,10 +534,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -658,29 +553,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; + }]; - // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadSections) + [_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -690,24 +588,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; - - // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveSection) { + [_delegate dataController:self didMoveSection:section toSection:newSection]; + } + }]; }]; }]; } @@ -775,7 +663,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -795,7 +688,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -810,20 +707,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadNodes) + [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -869,7 +771,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -879,26 +781,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; // Don't re-calculate size for moving - NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveNode) { + [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + }]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; -} - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; +- (NSMutableDictionary *)editingNode{ + return _editingNodes; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -965,11 +864,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; + return _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d5288f40e2..c3e6f31551 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for nodes reload. + * + * @param rangeController Sender. + * + * @param nodes Inserted nodes. + * + * @param indexPaths Index path of reloaded nodes. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + * Called for movement of node. + * + * @param rangeController Sender. + * + * @param fromIndexPath Index path of moved node before the movement. + * + * @param toIndexPath Index path of moved node after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** * Called for section insertion. * @@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for section reload. + * + * @param rangeController Sender. + * + * @param indexSet Index set of reloaded sections. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + /** * Called for section deletion. * @@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for movement of section. + * + * @param rangeController Sender. + * + * @param fromIndex Index of moved section before the movement. + * + * @param toIndex Index of moved section after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + * Called for reload data. + * + * @param rangeController Sender. + */ +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController; + @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b8d7d8f89..c13ef1cdf6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,15 +361,30 @@ }); } -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{ + ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASPerformBlockOnMainThread(^{ @@ -378,4 +393,11 @@ }); } -@end \ No newline at end of file +- (void)dataControllerDidReloadData:(ASDataController *)dataController{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeControllerDidReloadData:self]; + }); +} + +@end From 7f57e8cbf4682d513e327b205ddf26722738387a Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Fri, 19 Feb 2016 00:09:21 -0800 Subject: [PATCH 150/224] fix scheduling issue that causes collectionView to not animate --- AsyncDisplayKit/Details/ASDataController.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7a7d28e446..b4754a0382 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -445,9 +445,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { _batchUpdateCounter--; - if (_batchUpdateCounter == 0) { + if (_batchUpdateCounter == 0 && _pendingEditCommandBlocks.count > 0) { LOG(@"endUpdatesWithCompletion - beginning"); + [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + [_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataControllerBeginUpdates:self]; }]; @@ -461,8 +463,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; [_pendingEditCommandBlocks removeAllObjects]; - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + }]; }]; } } From d1793d50c889e834ace23383f235b88a2f67f2d8 Mon Sep 17 00:00:00 2001 From: Engin Kurutepe Date: Fri, 19 Feb 2016 14:32:04 +0100 Subject: [PATCH 151/224] fix umbrella header --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++-- AsyncDisplayKit/AsyncDisplayKit.h | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ac70348372..93ad4e815f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -422,7 +422,7 @@ B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; }; B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */; }; - B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; }; B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1441,7 +1441,6 @@ AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, - B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, @@ -1519,6 +1518,7 @@ 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, + B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 196bc1eb82..b703f44bc1 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -51,8 +51,11 @@ #import #import -#import #import +#import +#import +#import +#import #import #import #import From e2da2532ce185596da006e5f56703353b0afeb87 Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Fri, 19 Feb 2016 12:12:56 -0800 Subject: [PATCH 152/224] fix tests --- AsyncDisplayKit/ASTableView.mm | 2 +- AsyncDisplayKit/Details/ASDataController.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bbcfb42bba..0147dd5ec6 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -897,7 +897,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex { ASDisplayNodeAssertMainThread(); - LOG(@"UITableView moveSection:%@", indexSet); + LOG(@"UITableView moveSection:%@", fromIndex); if (!self.asyncDataSource) { diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index b4754a0382..d1850f1ece 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -445,7 +445,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { _batchUpdateCounter--; - if (_batchUpdateCounter == 0 && _pendingEditCommandBlocks.count > 0) { + if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; From 6f3d570f13780e5dcdd4e183c857f9f1de256373 Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Fri, 19 Feb 2016 12:45:43 -0800 Subject: [PATCH 153/224] appleguy's comment --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- AsyncDisplayKit/Details/ASDataController.mm | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ac70348372..94e9a5bbf8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -694,7 +694,7 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 0147dd5ec6..5ef50f7d88 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -897,7 +897,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex { ASDisplayNodeAssertMainThread(); - LOG(@"UITableView moveSection:%@", fromIndex); + LOG(@"UITableView moveSection:%ld", (long)fromIndex); if (!self.asyncDataSource) { diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index d1850f1ece..40bf365c25 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -448,10 +448,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataControllerBeginUpdates:self]; + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -464,6 +464,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_pendingEditCommandBlocks removeAllObjects]; [_editingTransactionQueue addOperationWithBlock:^{ + // scheduling this block on _editingTransactionQueue is crucial. + // we must wait for all edit command blocks that happened before this one to schedule their main thread blocks first. [_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; From 03446d97e199e61221cb9cfbab09b603d17a7db7 Mon Sep 17 00:00:00 2001 From: Max Gu Date: Thu, 18 Feb 2016 18:00:37 -0800 Subject: [PATCH 154/224] Adding scroll visibility --- AsyncDisplayKit/ASCellNode+Internal.h | 15 ++++++++ AsyncDisplayKit/ASCellNode.h | 21 +++++++++++ AsyncDisplayKit/ASCellNode.m | 24 +++++++++++-- AsyncDisplayKit/ASCollectionView.mm | 43 +++++++++++++++++++++-- AsyncDisplayKit/Details/ASDelegateProxy.m | 3 ++ 5 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 AsyncDisplayKit/ASCellNode+Internal.h diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h new file mode 100644 index 0000000000..feb816467d --- /dev/null +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -0,0 +1,15 @@ +// +// ASCellNode+Internal.h +// Pods +// +// Created by Max Gu on 2/19/16. +// +// + +#import "ASCellNode.h" + +@interface ASCellNode (Internal) + +@property (nonatomic, assign) BOOL shouldObserveVisibility; + +@end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index d1e575383e..1267ff0920 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -16,6 +16,7 @@ typedef NSUInteger ASCellNodeAnimation; @protocol ASCellNodeLayoutDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView cellFrameInScrollView:(CGRect)cellFrame; /** * Notifies the delegate that the specified cell node has done a relayout. * The notification is done on main thread. @@ -24,6 +25,19 @@ typedef NSUInteger ASCellNodeAnimation; * @param sizeChanged `YES` if the node's `calculatedSize` changed during the relayout, `NO` otherwise. */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; + +<<<<<<< HEAD +======= +@optional +/** + * Notifies the delegate that the specified cell node has scrolled + * + * @param node: A node informing the delegate about the scroll + * @param cellFrame: The frame of the cell that has just scrolled + */ +- (void)visibleNodeDidScroll:(ASCellNode *)node inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +>>>>>>> 4b8216f... Adding scroll visibility + @end /** @@ -75,6 +89,7 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, weak) id layoutDelegate; +@property (nonatomic, assign) BOOL shouldObserveVisibility; /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. @@ -107,6 +122,12 @@ typedef NSUInteger ASCellNodeAnimation; */ - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; +<<<<<<< HEAD +- (void)updateScrollSituationWithScrollVIew:(UIScrollView *)scrollView; +======= +- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; + +>>>>>>> 4b8216f... Adding scroll visibility @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index df7040d92e..8ed29380a0 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -6,9 +6,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASCellNode.h" +#import "ASCellNode+Internal.h" #import "ASInternalHelpers.h" +#import "ASCollectionView.h" #import #import #import @@ -36,7 +37,9 @@ // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; - + if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { + self.shouldObserveVisibility= YES; + } return self; } @@ -57,6 +60,9 @@ [self addSubnode:_viewControllerNode]; } + if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { + self.shouldObserveVisibility = YES; + } return self; } @@ -134,6 +140,20 @@ [(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event]; } +<<<<<<< HEAD +- (void)updateScrollSituationWithScrollVIew:(UIScrollView *)scrollView +{ + // TODO(Max): Fix the cellFrame here + [self.layoutDelegate scrollViewDidScroll:scrollView cellFrameInScrollView:CGRectZero]; +======= +- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame +{ + if (layoutDelegateImplementsVisibleNodeDidScroll) { + [self.layoutDelegate visibleNodeDidScroll:self inScrollView:scrollView withCellFrame:cellFrame]; + } +>>>>>>> 4b8216f... Adding scroll visibility +} + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b9f58ee5ff..cd31312cb9 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,6 +9,7 @@ #import "ASAssert.h" #import "ASBatchFetching.h" #import "ASDelegateProxy.h" +#import "ASCellNode+Internal.h" #import "ASCollectionNode.h" #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" @@ -67,7 +68,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; - +<<<<<<< HEAD + NSMutableArray *_cellsForVisibilityUpdates; +======= + NSMutableSet *_cellsForVisibilityUpdates; +>>>>>>> 4b8216f... Adding scroll visibility id _layoutFacilitator; BOOL _performingBatchUpdates; @@ -75,6 +80,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; BOOL _asyncDataFetchingEnabled; BOOL _asyncDelegateImplementsInsetSection; + BOOL _asyncDelegateImplementsScrollviewDidScroll; BOOL _collectionViewLayoutImplementsInsetSection; BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath; @@ -212,6 +218,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _registeredSupplementaryKinds = [NSMutableSet set]; +<<<<<<< HEAD + _cellsForVisibilityUpdates = [[NSMutableArray alloc] init]; +======= + _cellsForVisibilityUpdates = [NSMutableSet set]; +>>>>>>> 4b8216f... Adding scroll visibility self.backgroundColor = [UIColor whiteColor]; [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kCellReuseIdentifier]; @@ -328,12 +339,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _asyncDelegateImplementsInsetSection = NO; + _asyncDelegateImplementsScrollviewDidScroll = NO; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); + _asyncDelegateImplementsScrollviewDidScroll = ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)] ? 1 : 0); } - + super.delegate = (id)_proxyDelegate; [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; @@ -589,6 +602,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } + [_cellsForVisibilityUpdates addObject:cell]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath @@ -600,6 +614,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; } + [_cellsForVisibilityUpdates removeObject:cell]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { @@ -650,6 +665,30 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +<<<<<<< HEAD +- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ + for (ASCellNode *node in _cellsForVisibilityUpdates) { + if (node.shouldObserveVisibility) { + [node updateScrollSituationWithScrollVIew:scrollView]; + } + } + if ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +======= +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + for (ASCellNode *node in _cellsForVisibilityUpdates) { + if (node.shouldObserveVisibility) { + [node _visibleNodeDidScroll:scrollView withCellFrame:node.frame]; + } + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +>>>>>>> 4b8216f... Adding scroll visibility +} + - (BOOL)shouldBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 2af0e7c7d5..baa65a65c7 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -56,6 +56,9 @@ // used for batch fetching API selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + // used for ASCellNode visibility + selector == @selector(scrollViewDidScroll:) || + // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) selector == @selector(collectionView:canMoveItemAtIndexPath:) || selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) || From 274029a5e05e71dd8db8c4f104b806ecdffe410e Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 13:02:02 -0800 Subject: [PATCH 155/224] Removing an unnecessary property --- AsyncDisplayKit/ASCellNode.h | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index ecb616e09f..7f8cc01238 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -85,7 +85,6 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, weak) id layoutDelegate; -@property (nonatomic, assign) BOOL shouldObserveVisibility; /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. From a834382bcbc521f236d96689d6c6644a4df903fc Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 14:27:22 -0800 Subject: [PATCH 156/224] Update for visibility monitoring --- AsyncDisplayKit/ASCellNode+Internal.h | 2 +- AsyncDisplayKit/ASCellNode.m | 6 +++--- AsyncDisplayKit/ASCollectionView.mm | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index feb816467d..5481965995 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -10,6 +10,6 @@ @interface ASCellNode (Internal) -@property (nonatomic, assign) BOOL shouldObserveVisibility; +@property (nonatomic, assign) BOOL shouldMonitorScrollViewDidScroll; @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 61458d5ac0..42f5b0b971 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -38,7 +38,7 @@ _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { - self.shouldObserveVisibility= YES; + self.shouldMonitorScrollViewDidScroll = YES; } return self; } @@ -61,7 +61,7 @@ } if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { - self.shouldObserveVisibility = YES; + self.shouldMonitorScrollViewDidScroll = YES; } return self; } @@ -142,7 +142,7 @@ - (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame { - if (self.shouldObserveVisibility) { + if (self.shouldMonitorScrollViewDidScroll) { [self.layoutDelegate visibleNodeDidScroll:self inScrollView:scrollView withCellFrame:cellFrame]; } } diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 1f9a7ff89b..35ddd61c22 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -673,9 +673,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - for (ASCellNode *node in _cellsForVisibilityUpdates) { - if (node.shouldObserveVisibility) { - [node _visibleNodeDidScroll:scrollView withCellFrame:node.frame]; + for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { + ASCellNode *node = [collectionCell node]; + if (node.shouldMonitorScrollViewDidScroll) { + NSLog(@"Calling _visibleNodeDidScroll"); + [node _visibleNodeDidScroll:scrollView withCellFrame: node.frame]; } } if (_asyncDelegateImplementsScrollviewDidScroll) { From 53cbd643de111c42969cb7a3f1db730a590d3a64 Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 14:56:02 -0800 Subject: [PATCH 157/224] Removing ASCellNode+Internal --- AsyncDisplayKit/ASCellNode+Internal.h | 15 --------------- AsyncDisplayKit/ASCellNode.h | 2 ++ AsyncDisplayKit/ASCellNode.m | 3 ++- AsyncDisplayKit/ASCollectionView.mm | 2 -- 4 files changed, 4 insertions(+), 18 deletions(-) delete mode 100644 AsyncDisplayKit/ASCellNode+Internal.h diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h deleted file mode 100644 index 5481965995..0000000000 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASCellNode+Internal.h -// Pods -// -// Created by Max Gu on 2/19/16. -// -// - -#import "ASCellNode.h" - -@interface ASCellNode (Internal) - -@property (nonatomic, assign) BOOL shouldMonitorScrollViewDidScroll; - -@end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 7f8cc01238..45da0fd1ce 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -85,6 +85,8 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, weak) id layoutDelegate; +@property (nonatomic, assign, readonly) BOOL shouldMonitorScrollViewDidScroll; + /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 42f5b0b971..7aa83fc9fe 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASCellNode+Internal.h" +#import "ASCellNode.h" #import "ASInternalHelpers.h" #import "ASCollectionView.h" @@ -24,6 +24,7 @@ UIViewController *_viewController; ASDisplayNode *_viewControllerNode; } +@property (nonatomic, assign, readwrite) BOOL shouldMonitorScrollViewDidScroll; @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 35ddd61c22..461fe2e702 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,7 +9,6 @@ #import "ASAssert.h" #import "ASBatchFetching.h" #import "ASDelegateProxy.h" -#import "ASCellNode+Internal.h" #import "ASCollectionNode.h" #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" @@ -676,7 +675,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { ASCellNode *node = [collectionCell node]; if (node.shouldMonitorScrollViewDidScroll) { - NSLog(@"Calling _visibleNodeDidScroll"); [node _visibleNodeDidScroll:scrollView withCellFrame: node.frame]; } } From 382509fac84d0e39fdcd993e50c1e292788fbe01 Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 21:30:36 -0800 Subject: [PATCH 158/224] Letting ASCellNode and its subclasses handle didScroll --- AsyncDisplayKit/ASCellNode+Internal.h | 15 +++++++++++++++ AsyncDisplayKit/ASCellNode.h | 10 ---------- AsyncDisplayKit/ASCellNode.m | 14 ++------------ AsyncDisplayKit/ASCollectionView.mm | 15 ++++++++++----- 4 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 AsyncDisplayKit/ASCellNode+Internal.h diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h new file mode 100644 index 0000000000..2152d91bc3 --- /dev/null +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -0,0 +1,15 @@ +// +// ASCellNode+Internal.h +// Pods +// +// Created by Max Gu on 2/19/16. +// +// + +#import "ASCellNode.h" + +@interface ASCellNode (Internal) + +- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; + +@end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 45da0fd1ce..830ed49e8f 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -25,15 +25,6 @@ typedef NSUInteger ASCellNodeAnimation; */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; -@optional -/** - * Notifies the delegate that the specified cell node has scrolled - * - * @param node: A node informing the delegate about the scroll - * @param cellFrame: The frame of the cell that has just scrolled - */ -- (void)visibleNodeDidScroll:(ASCellNode *)node inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; - @end /** @@ -120,7 +111,6 @@ typedef NSUInteger ASCellNodeAnimation; - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; - (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; - @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 7aa83fc9fe..c007110680 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -9,7 +9,6 @@ #import "ASCellNode.h" #import "ASInternalHelpers.h" -#import "ASCollectionView.h" #import #import #import @@ -24,7 +23,6 @@ UIViewController *_viewController; ASDisplayNode *_viewControllerNode; } -@property (nonatomic, assign, readwrite) BOOL shouldMonitorScrollViewDidScroll; @end @@ -38,9 +36,6 @@ // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; - if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { - self.shouldMonitorScrollViewDidScroll = YES; - } return self; } @@ -60,10 +55,7 @@ [self addSubnode:_viewControllerNode]; } - - if ([self.layoutDelegate respondsToSelector:@selector(visibleNodeDidScroll:inScrollView:withCellFrame:)]) { - self.shouldMonitorScrollViewDidScroll = YES; - } + return self; } @@ -143,9 +135,7 @@ - (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame { - if (self.shouldMonitorScrollViewDidScroll) { - [self.layoutDelegate visibleNodeDidScroll:self inScrollView:scrollView withCellFrame:cellFrame]; - } + // To be overriden by subclasses } @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 461fe2e702..eb456bba15 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,6 +9,7 @@ #import "ASAssert.h" #import "ASBatchFetching.h" #import "ASDelegateProxy.h" +#import "ASCellNode+Internal.h" #import "ASCollectionNode.h" #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" @@ -541,7 +542,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } - [_cellsForVisibilityUpdates addObject:cell]; + if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(_visibleNodeDidScroll:withCellFrame:))) { + [_cellsForVisibilityUpdates addObject:cell]; + } } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath @@ -553,7 +556,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; } - [_cellsForVisibilityUpdates removeObject:cell]; + if ([_cellsForVisibilityUpdates containsObject:cell]) { + [_cellsForVisibilityUpdates removeObject:cell]; + } + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { @@ -674,9 +680,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { ASCellNode *node = [collectionCell node]; - if (node.shouldMonitorScrollViewDidScroll) { - [node _visibleNodeDidScroll:scrollView withCellFrame: node.frame]; - } + // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates + [node _visibleNodeDidScroll:scrollView withCellFrame:collectionCell.frame]; } if (_asyncDelegateImplementsScrollviewDidScroll) { [_asyncDelegate scrollViewDidScroll:scrollView]; From a1061301e08a269c416ca7844b661a444ebcf012 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 19 Feb 2016 21:31:39 -0800 Subject: [PATCH 159/224] [ASDataController] Revert the reloadData optimizations again - need to fix apps relying on prior behavior. The optimization seems correct now, but apps like Pinterest have some core code relying on edit operation order that is actually not permitted by UIKit (and this diff) but were tolerated previously. We will re-land this once we have time to adapt the code. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 +- AsyncDisplayKit/ASCollectionView.mm | 105 ++++- AsyncDisplayKit/ASTableView.mm | 83 +++- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 81 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 376 +++++++----------- AsyncDisplayKit/Details/ASRangeController.h | 53 +++ AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 477 insertions(+), 339 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e57d0a3ff2..94e9a5bbf8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -647,7 +647,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -694,7 +694,7 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b69e685ed7..07484f3622 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController reloadDataImmediately]; [super reloadData]; } @@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -951,6 +951,46 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadItemsAtIndexPaths:indexPaths]; + }]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -971,6 +1011,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super reloadSections:indexSet]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; + [UIView performWithoutAnimation:^{ + [super reloadSections:indexSet]; + }]; + } +} + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -991,6 +1051,43 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES]; + [_batchUpdateBlocks addObject:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } else { + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO]; + [UIView performWithoutAnimation:^{ + [super moveSection:fromIndex toSection:toIndex]; + }]; + } +} + +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + if (!self.asyncDataSource || _superIsPendingDataLoad) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + if (_performingBatchUpdates) { + [_batchUpdateBlocks addObject:^{ + [super reloadData]; + }]; + } else { + [UIView performWithoutAnimation:^{ + [super reloadData]; + }]; + } +} + #pragma mark - ASCellNodeDelegate - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 99009d8eb9..5ef50f7d88 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASPerformBlockOnMainThread(^{ - [super reloadData]; - }); - [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; - [super reloadData]; + [_dataController reloadDataImmediately]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } #pragma mark - @@ -836,6 +832,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); + + if (_automaticallyAdjustsContentOffset) { + [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; + } +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ + ASDisplayNodeAssertMainThread(); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; +} + - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -852,6 +878,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadSections:%@", indexSet); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; + ASPerformBlockWithoutAnimation(preventAnimation, ^{ + [super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }); +} + +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex +{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView moveSection:%ld", (long)fromIndex); + + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super moveSection:fromIndex toSection:toIndex]; +} + + - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -867,6 +923,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ + ASDisplayNodeAssertMainThread(); + LOG(@"UITableView reloadData"); + + if (!self.asyncDataSource) { + return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes + } + + [super reloadData]; +} + #pragma mark - ASDataControllerDelegate - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 666ea86ce7..bdc0c4993c 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; + [super moveSection:section toSection:newSection]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..979acd30e3 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,37 +49,27 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - - // Insert each section + // Insert sections NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + self.editingNode[kind] = sections; + + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -91,9 +81,6 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -104,23 +91,22 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } @@ -132,40 +118,31 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; + }]; + // reinsert the elements + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - }]; - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + [self moveSection:section ofKind:kind toSection:newSection]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..54f1fc259a 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,16 +14,7 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying - -/** - * Provides a collection of index paths for nodes of the given kind that are currently in the editing store - */ -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; - -/** - * Read-only access to the underlying editing nodes of the given kind - */ -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; +@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; /** * Read only access to the underlying completed nodes of the given kind @@ -35,7 +26,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -53,24 +44,34 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. + * Inserts the given nodes of the specified kind into the backing store. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. + * Deletes the given nodes of the specified kind in the backing store. */ -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; /** - * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Inserts the given sections of the specified kind in the backing store. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; /** - * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. + * Deletes the given sections of the specified kind in the backing store. */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; + +/** + * Moves the given section of the specified kind in the backing store. + */ +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection; + +/** + * Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished. + */ +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6648275d9b..1766b7dfc0 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of elements. + */ +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of elements. + */ +- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of sections. + */ +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of sections. + */ +- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + Called for reload data. + */ +- (void)dataControllerDidReloadData:(ASDataController *)dataController; + @end /** @@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; -/** @name Initial loading - * - * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, - * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. - */ - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** @name Data Updating */ - (void)beginUpdates; @@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; +- (void)reloadDataWithCompletion:(void (^)())completion; -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)reloadDataImmediately; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 32d62adac2..40bf365c25 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; + BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; + BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + BOOL _delegateDidReloadSections; + BOOL _delegateDidMoveSection; + BOOL _delegateDidReloadData; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -92,8 +96,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; + _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)]; + _delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; + _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - - // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); - NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; - NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; - } + [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -144,21 +150,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); } -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (!nodes.count) { - return; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + return; } NSUInteger nodeCount = nodes.count; @@ -238,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) return; + LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; } -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) { - return; + return @[]; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); NSMutableArray *editingNodes = _editingNodes[kind]; + NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - - [_mainSerialQueue performBlockOnMainThread:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + return deletedNodes; } -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{ if (indexSet.count == 0) return; + LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; } -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet { if (indexSet.count == 0) return; + + LOG(@"deleteSectionsOfKind:%@", kind); [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; +} + +- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection +{ + NSArray *movedSection = _editingNodes[kind][section]; + [_editingNodes[kind] removeObjectAtIndex:section]; + [_editingNodes[kind] insertObject:movedSection atIndex:newSection]; +} + +- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock +{ + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]); + [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + _completedNodes[kind] = completedNodes; if (completionBlock) { - completionBlock(indexSet); + completionBlock(); } }]; } -#pragma mark - Internal Data Querying + Editing +#pragma mark - Reload (External API) -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataWithCompletion:(void (^)())completion { - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:NO completion:completion]; } -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)reloadDataImmediately { - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:YES completion:nil]; } -/** - * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indicies from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - [self accessDataSourceWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; - - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; - - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; - } - } - - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; -} - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion +- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -417,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; void (^transactionBlock)() = ^{ LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - + [self willReloadData]; - - // Insert each section + + // Insert sections NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - - [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; + _editingNodes[ASDataControllerRowNodeKind] = sections; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadData) { + [_delegate dataControllerDidReloadData:self]; + } + if (completion) { + completion(); + } + }]; + }]; }; if (synchronously) { @@ -550,11 +450,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; }]; }]; @@ -567,13 +462,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - + [_editingTransactionQueue addOperationWithBlock:^{ + // scheduling this block on _editingTransactionQueue is crucial. + // we must wait for all edit command blocks that happened before this one to schedule their main thread blocks first. [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; }]; @@ -607,9 +500,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -622,8 +515,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; + + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -641,10 +540,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -658,29 +559,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; + }]; - // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadSections) + [_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -690,24 +594,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; - - // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveSection) { + [_delegate dataController:self didMoveSection:section toSection:newSection]; + } + }]; }]; }]; } @@ -775,7 +669,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -795,7 +694,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -810,20 +713,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadNodes) + [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -869,7 +777,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -879,26 +787,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; // Don't re-calculate size for moving - NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveNode) { + [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + }]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; -} - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; +- (NSMutableDictionary *)editingNode{ + return _editingNodes; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -965,11 +870,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; + return _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d5288f40e2..c3e6f31551 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for nodes reload. + * + * @param rangeController Sender. + * + * @param nodes Inserted nodes. + * + * @param indexPaths Index path of reloaded nodes. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + * Called for movement of node. + * + * @param rangeController Sender. + * + * @param fromIndexPath Index path of moved node before the movement. + * + * @param toIndexPath Index path of moved node after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** * Called for section insertion. * @@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for section reload. + * + * @param rangeController Sender. + * + * @param indexSet Index set of reloaded sections. + * + * @param animationOptions Animation options. See ASDataControllerAnimationOptions. + */ +- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + /** * Called for section deletion. * @@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + * Called for movement of section. + * + * @param rangeController Sender. + * + * @param fromIndex Index of moved section before the movement. + * + * @param toIndex Index of moved section after the movement. + */ +- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; + +/** + * Called for reload data. + * + * @param rangeController Sender. + */ +- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController; + @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b8d7d8f89..c13ef1cdf6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,15 +361,30 @@ }); } -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{ + ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } +- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASPerformBlockOnMainThread(^{ @@ -378,4 +393,11 @@ }); } -@end \ No newline at end of file +- (void)dataControllerDidReloadData:(ASDataController *)dataController{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeControllerDidReloadData:self]; + }); +} + +@end From 115fc2b3da60a591b8be03b5f122cf05b317a92a Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 22:15:44 -0800 Subject: [PATCH 160/224] Adding visibility monitoring for ASTableVieww --- AsyncDisplayKit/ASCellNode.h | 5 ++-- AsyncDisplayKit/ASCellNode.m | 2 +- AsyncDisplayKit/ASCollectionView.mm | 6 ++--- AsyncDisplayKit/ASTableView.mm | 29 +++++++++++++++++++---- AsyncDisplayKit/Details/ASDelegateProxy.m | 3 +++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 830ed49e8f..8788d30015 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -76,8 +76,6 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, weak) id layoutDelegate; -@property (nonatomic, assign, readonly) BOOL shouldMonitorScrollViewDidScroll; - /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. @@ -110,7 +108,8 @@ typedef NSUInteger ASCellNodeAnimation; */ - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; -- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +- (void)visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; + @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index c007110680..3e1fccdbc8 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -133,7 +133,7 @@ [(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event]; } -- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame +- (void)visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame { // To be overriden by subclasses } diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index eb456bba15..bd60b03d82 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -530,7 +530,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return cell; } -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; @@ -538,7 +538,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } - ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; + ASCellNode *cellNode = [cell node]; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } @@ -681,7 +681,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { ASCellNode *node = [collectionCell node]; // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates - [node _visibleNodeDidScroll:scrollView withCellFrame:collectionCell.frame]; + [node visibleNodeDidScroll:scrollView withCellFrame:collectionCell.frame]; } if (_asyncDelegateImplementsScrollviewDidScroll) { [_asyncDelegate scrollViewDidScroll:scrollView]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bbcfb42bba..272e6c4f46 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -112,6 +112,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL _queuedNodeHeightUpdate; BOOL _isDeallocating; BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath; + BOOL _asyncDelegateImplementsScrollviewDidScroll; + NSMutableSet *_cellsForVisibilityUpdates; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -197,7 +199,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!(self = [super initWithFrame:frame style:style])) { return nil; } - + _cellsForVisibilityUpdates = [NSMutableSet set]; if (!dataControllerClass) { dataControllerClass = [[self class] dataControllerClass]; } @@ -585,7 +587,18 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return direction; } -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { + ASCellNode *node = [tableCell node]; + [node visibleNodeDidScroll:scrollView withCellFrame:tableCell.frame]; + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} + +- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { _pendingVisibleIndexPath = indexPath; @@ -595,13 +608,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } - ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath]; + ASCellNode *cellNode = [cell node]; + + if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(visibleNodeDidScroll:withCellFrame:))) { + [_cellsForVisibilityUpdates addObject:cell]; + } if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } } -- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath +- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath { if ([_pendingVisibleIndexPath isEqual:indexPath]) { _pendingVisibleIndexPath = nil; @@ -615,6 +632,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self didEndDisplayingNode:node forRowAtIndexPath:indexPath]; } + if ([_cellsForVisibilityUpdates containsObject:cell]) { + [_cellsForVisibilityUpdates removeObject:cell]; + } + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) { diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index baa65a65c7..3034a83d86 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -24,6 +24,9 @@ selector == @selector(numberOfSectionsInTableView:) || selector == @selector(tableView:numberOfRowsInSection:) || + // used for ASCellNode visibility + selector == @selector(scrollViewDidScroll:) || + // used for ASRangeController visibility updates selector == @selector(tableView:willDisplayCell:forRowAtIndexPath:) || selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || From 548b600504ae39ce037a1f3fa423a58de6ccd7eb Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 22:18:08 -0800 Subject: [PATCH 161/224] Removing 2 unnecessary checks --- AsyncDisplayKit/ASCollectionView.mm | 5 ++--- AsyncDisplayKit/ASTableView.mm | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index bd60b03d82..f26f38a85a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -556,9 +556,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath]; } - if ([_cellsForVisibilityUpdates containsObject:cell]) { - [_cellsForVisibilityUpdates removeObject:cell]; - } + [_cellsForVisibilityUpdates removeObject:cell]; + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 272e6c4f46..a9f4d2aff9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -632,9 +632,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self didEndDisplayingNode:node forRowAtIndexPath:indexPath]; } - if ([_cellsForVisibilityUpdates containsObject:cell]) { - [_cellsForVisibilityUpdates removeObject:cell]; - } + [_cellsForVisibilityUpdates removeObject:cell]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" From 67c8cd5ccf90b2a44b69cc87a1de4d4f3e2622e7 Mon Sep 17 00:00:00 2001 From: Max Gu Date: Fri, 19 Feb 2016 22:21:07 -0800 Subject: [PATCH 162/224] Removing underscore --- AsyncDisplayKit/ASCellNode+Internal.h | 2 -- AsyncDisplayKit/ASCollectionView.mm | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index 2152d91bc3..a660e46d80 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -10,6 +10,4 @@ @interface ASCellNode (Internal) -- (void)_visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; - @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index f26f38a85a..20e30b7c40 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -542,7 +542,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; } - if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(_visibleNodeDidScroll:withCellFrame:))) { + if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(visibleNodeDidScroll:withCellFrame:))) { [_cellsForVisibilityUpdates addObject:cell]; } } From 2b10d84a2b972ea1e53cbb4be79b6686c3be3b94 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 19 Feb 2016 23:26:12 -0800 Subject: [PATCH 163/224] Revert "fix scheduling issue that causes collectionView to not animate" --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- AsyncDisplayKit/Details/ASDataController.mm | 14 ++++---------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 94e9a5bbf8..ac70348372 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -694,7 +694,7 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index c2e7011e62..a9f4d2aff9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -916,7 +916,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex { ASDisplayNodeAssertMainThread(); - LOG(@"UITableView moveSection:%ld", (long)fromIndex); + LOG(@"UITableView moveSection:%@", indexSet); if (!self.asyncDataSource) { diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 40bf365c25..7a7d28e446 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -448,10 +448,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_editingTransactionQueue addOperationWithBlock:^{ - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; - }]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataControllerBeginUpdates:self]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -463,12 +461,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; [_pendingEditCommandBlocks removeAllObjects]; - [_editingTransactionQueue addOperationWithBlock:^{ - // scheduling this block on _editingTransactionQueue is crucial. - // we must wait for all edit command blocks that happened before this one to schedule their main thread blocks first. - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; } } From b75d6627bb57ded3ae079071e1e10558400f4324 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 19 Feb 2016 23:26:29 -0800 Subject: [PATCH 164/224] Revert "Revert "Revert "[ASCollectionView / ASTableView] Optimize reloadData and reloadSection: methods.""" --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.mm | 105 +---- AsyncDisplayKit/ASTableView.mm | 83 +--- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 79 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 400 +++++++++++------- AsyncDisplayKit/Details/ASRangeController.h | 53 --- AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 352 insertions(+), 484 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ac70348372..e57d0a3ff2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -647,7 +647,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 20e30b7c40..77fc3dd60d 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -252,7 +252,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithCompletion:completion]; + [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; } - (void)reloadData @@ -264,7 +264,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; [super reloadData]; } @@ -451,7 +451,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -475,7 +475,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -974,46 +974,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super reloadItemsAtIndexPaths:indexPaths]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - [UIView performWithoutAnimation:^{ - [super reloadItemsAtIndexPaths:indexPaths]; - }]; - } -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO]; - [UIView performWithoutAnimation:^{ - [super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; - }]; - } -} - - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -1034,26 +994,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super reloadSections:indexSet]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; - [UIView performWithoutAnimation:^{ - [super reloadSections:indexSet]; - }]; - } -} - - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -1074,43 +1014,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES]; - [_batchUpdateBlocks addObject:^{ - [super moveSection:fromIndex toSection:toIndex]; - }]; - } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO]; - [UIView performWithoutAnimation:^{ - [super moveSection:fromIndex toSection:toIndex]; - }]; - } -} - -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (_performingBatchUpdates) { - [_batchUpdateBlocks addObject:^{ - [super reloadData]; - }]; - } else { - [UIView performWithoutAnimation:^{ - [super reloadData]; - }]; - } -} - #pragma mark - ASCellNodeDelegate - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index a9f4d2aff9..923e690236 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -305,7 +305,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - [_dataController reloadDataWithCompletion:completion]; + ASPerformBlockOnMainThread(^{ + [super reloadData]; + }); + [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; } - (void)reloadData @@ -316,7 +319,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; + [super reloadData]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -431,7 +435,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -455,7 +459,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; } #pragma mark - @@ -851,36 +855,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }); - - if (_automaticallyAdjustsContentOffset) { - [self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES]; - } -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath -{ - ASDisplayNodeAssertMainThread(); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; -} - - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -897,36 +871,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadSections:%@", indexSet); - - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - [super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }); -} - -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex -{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView moveSection:%@", indexSet); - - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [super moveSection:fromIndex toSection:toIndex]; -} - - - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); @@ -942,17 +886,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; }); } -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{ - ASDisplayNodeAssertMainThread(); - LOG(@"UITableView reloadData"); - - if (!self.asyncDataSource) { - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - [super reloadData]; -} - #pragma mark - ASDataControllerDelegate - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index bdc0c4993c..666ea86ce7 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection]; + [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 979acd30e3..2408a57e1f 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,27 +49,37 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Insert sections + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + + NSArray *editingNodes = [self editingNodesOfKind:kind]; + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; + + // Insert each section NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - self.editingNode[kind] = sections; - - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; + + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -81,6 +91,9 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -91,22 +104,23 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - [self deleteSectionsOfKind:kind atIndexSet:sections]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; } } @@ -118,31 +132,40 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - [self moveSection:section ofKind:kind toSection:newSection]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + + // update the section of indexpaths + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + }]; + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 54f1fc259a..32d787910e 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,7 +14,16 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying -@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; + +/** + * Provides a collection of index paths for nodes of the given kind that are currently in the editing store + */ +- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind; + +/** + * Read-only access to the underlying editing nodes of the given kind + */ +- (NSMutableArray *)editingNodesOfKind:(NSString *)kind; /** * Read only access to the underlying completed nodes of the given kind @@ -26,7 +35,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. @@ -44,34 +53,24 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store. + * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Deletes the given nodes of the specified kind in the backing store. + * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. */ -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Inserts the given sections of the specified kind in the backing store. + * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; /** - * Deletes the given sections of the specified kind in the backing store. + * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; - -/** - * Moves the given section of the specified kind in the backing store. - */ -- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection; - -/** - * Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished. - */ -- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 1766b7dfc0..6648275d9b 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,41 +91,16 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - Called for reload of elements. - */ -- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for movement of elements. - */ -- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; - /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - Called for reload of sections. - */ -- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - Called for movement of sections. - */ -- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; - -/** - Called for reload data. - */ -- (void)dataControllerDidReloadData:(ASDataController *)dataController; - @end /** @@ -162,6 +137,14 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; +/** @name Initial loading + * + * @discussion This method allows choosing an animation style for the first load of content. It is typically used just once, + * for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource. + */ + +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + /** @name Data Updating */ - (void)beginUpdates; @@ -176,7 +159,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -192,11 +175,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadDataWithCompletion:(void (^)())completion; +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; -- (void)reloadDataImmediately; +- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7a7d28e446..32d62adac2 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,6 +30,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { + NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -41,14 +42,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; - BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; - BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; - BOOL _delegateDidReloadSections; - BOOL _delegateDidMoveSection; - BOOL _delegateDidReloadData; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -96,13 +92,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)]; - _delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; - _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; - _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -119,14 +110,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; + + // Processing in batches + for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { + NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); + NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; + NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; + [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; + } } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -150,13 +144,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); } +/** + * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. + */ +- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + // Insert finished nodes into data storage + [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; +} + - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (!nodes.count) { - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - return; + return; } NSUInteger nodeCount = nodes.count; @@ -236,84 +238,178 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (indexPaths.count == 0) return; - LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); + + [_mainSerialQueue performBlockOnMainThread:^{ + _completedNodes[kind] = completedNodes; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (indexPaths.count == 0) { - return @[]; + return; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; - NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - return deletedNodes; + + [_mainSerialQueue performBlockOnMainThread:^{ + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock +{ if (indexSet.count == 0) return; - LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; -} - -- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet -{ - if (indexSet.count == 0) - return; - - LOG(@"deleteSectionsOfKind:%@", kind); - [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; -} - -- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection -{ - NSArray *movedSection = _editingNodes[kind][section]; - [_editingNodes[kind] removeObjectAtIndex:section]; - [_editingNodes[kind] insertObject:movedSection atIndex:newSection]; -} - -- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock -{ - NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]); - + + // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. + NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); + [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; + [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { - completionBlock(); + completionBlock(sections, indexSet); } }]; } -#pragma mark - Reload (External API) - -- (void)reloadDataWithCompletion:(void (^)())completion +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock { - [self _reloadDataSynchronously:NO completion:completion]; + if (indexSet.count == 0) + return; + [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + if (completionBlock) { + completionBlock(indexSet); + } + }]; } -- (void)reloadDataImmediately +#pragma mark - Internal Data Querying + Editing + +/** + * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. + * + * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy + * of the editing nodes. The delegate is invoked on the main thread. + */ +- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self _reloadDataSynchronously:YES completion:nil]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; } -- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion +/** + * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. + * + * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. + * Once the backing stores are consistent, the delegate is invoked on the main thread. + */ +- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; +} + +/** + * Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate. + * + * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted + * in the completed store on the main thread. The delegate is invoked on the main thread. + */ +- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; + }]; +} + +/** + * Removes sections at the given indicies from the backing store and notifies the delegate. + * + * @discussion Section array are first removed from the editing store, then the associated section in the completed + * store is removed on the main thread. The delegate is invoked on the main thread. + */ +- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }]; +} + +#pragma mark - Initial Load & Full Reload (External API) + +- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [self accessDataSourceWithBlock:^{ + NSMutableArray *indexPaths = [NSMutableArray array]; + NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; + + // insert sections + [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; + + for (NSUInteger i = 0; i < sectionNum; i++) { + NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; + + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; + for (NSUInteger j = 0; j < rowNum; j++) { + [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; + } + } + + // insert elements + [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; +} + +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion +{ + [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; +} + +- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; +} + +- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -321,35 +417,39 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; void (^transactionBlock)() = ^{ LOG(@"Edit Transaction - reloadData"); - + + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + [self willReloadData]; - - // Insert sections + + // Insert each section NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - _editingNodes[ASDataControllerRowNodeKind] = sections; + + [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadData) { - [_delegate dataControllerDidReloadData:self]; - } - if (completion) { - completion(); - } - }]; - }]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + if (completion) { + dispatch_async(dispatch_get_main_queue(), completion); + } }; if (synchronously) { @@ -448,8 +548,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // Deep copy _completedNodes to _externalCompletedNodes. + // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. + _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); + + LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); + [_delegate dataControllerBeginUpdates:self]; + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -460,9 +567,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // Now that the transaction is done, _completedNodes can be accessed externally again. + _externalCompletedNodes = nil; + + LOG(@"endUpdatesWithCompletion - calling delegate end"); + [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + }]; }]; } } @@ -494,9 +607,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -509,14 +622,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; - - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; - }]; + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -534,12 +641,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; }]; }]; } @@ -553,32 +658,29 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadSections) - [_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; - }]; + // reinsert the elements + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -588,14 +690,24 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; + + // remove elements LOG(@"Edit Transaction - moveSection"); - [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveSection) { - [_delegate dataController:self didMoveSection:section toSection:newSection]; - } - }]; + + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + // update the section of indexpaths + NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSIndexPath *indexPath in indexPaths) { + [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + } + + // Don't re-calculate size for moving + [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; } @@ -663,12 +775,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -688,11 +795,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; }]; }]; } @@ -707,25 +810,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; - [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadNodes) - [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -771,7 +869,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -781,23 +879,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; + NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // Don't re-calculate size for moving - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveNode) { - [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; - } - }]; + NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSMutableDictionary *)editingNode{ - return _editingNodes; +- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; +} + +- (NSMutableArray *)editingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -864,10 +965,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } +/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _completedNodes[ASDataControllerRowNodeKind]; + return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index c3e6f31551..d5288f40e2 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,30 +163,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for nodes reload. - * - * @param rangeController Sender. - * - * @param nodes Inserted nodes. - * - * @param indexPaths Index path of reloaded nodes. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - * Called for movement of node. - * - * @param rangeController Sender. - * - * @param fromIndexPath Index path of moved node before the movement. - * - * @param toIndexPath Index path of moved node after the movement. - */ -- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; - /** * Called for section insertion. * @@ -198,17 +174,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for section reload. - * - * @param rangeController Sender. - * - * @param indexSet Index set of reloaded sections. - * - * @param animationOptions Animation options. See ASDataControllerAnimationOptions. - */ -- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - /** * Called for section deletion. * @@ -220,24 +185,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -/** - * Called for movement of section. - * - * @param rangeController Sender. - * - * @param fromIndex Index of moved section before the movement. - * - * @param toIndex Index of moved section after the movement. - */ -- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex; - -/** - * Called for reload data. - * - * @param rangeController Sender. - */ -- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController; - @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index c13ef1cdf6..1b8d7d8f89 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,30 +361,15 @@ }); } -- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{ - ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); -} - - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASPerformBlockOnMainThread(^{ @@ -393,11 +378,4 @@ }); } -- (void)dataControllerDidReloadData:(ASDataController *)dataController{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeControllerDidReloadData:self]; - }); -} - -@end +@end \ No newline at end of file From cd6ca2885e8f7719e7fe690232f0a3db284ced83 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 20 Feb 2016 13:00:01 -0800 Subject: [PATCH 165/224] [ASButtonNode] lazily initialize label, image, and backgroundImage (skip if never needed). --- AsyncDisplayKit/ASButtonNode.mm | 84 ++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 4831ba4fb3..460531cc0b 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -12,6 +12,7 @@ #import "ASDisplayNode+Subclasses.h" #import "ASBackgroundLayoutSpec.h" #import "ASInsetLayoutSpec.h" +#import "ASDisplayNode+Beta.h" @interface ASButtonNode () { @@ -42,35 +43,53 @@ @synthesize contentVerticalAlignment = _contentVerticalAlignment; @synthesize contentHorizontalAlignment = _contentHorizontalAlignment; @synthesize contentEdgeInsets = _contentEdgeInsets; +@synthesize titleNode = _titleNode; +@synthesize imageNode = _imageNode; +@synthesize backgroundImageNode = _backgroundImageNode; - (instancetype)init { - if (self = [super init]) { + if (self = [super init]) { + self.usesImplicitHierarchyManagement = YES; + _contentSpacing = 8.0; _laysOutHorizontally = YES; - - _titleNode = [[ASTextNode alloc] init]; - _imageNode = [[ASImageNode alloc] init]; - _backgroundImageNode = [[ASImageNode alloc] init]; - [_backgroundImageNode setContentMode:UIViewContentModeScaleToFill]; - - [_titleNode setLayerBacked:YES]; - [_imageNode setLayerBacked:YES]; - [_backgroundImageNode setLayerBacked:YES]; - - [_titleNode setFlexShrink:YES]; - _contentHorizontalAlignment = ASAlignmentMiddle; _contentVerticalAlignment = ASAlignmentCenter; _contentEdgeInsets = UIEdgeInsetsZero; - - [self addSubnode:_backgroundImageNode]; - [self addSubnode:_titleNode]; - [self addSubnode:_imageNode]; } return self; } +- (ASTextNode *)titleNode +{ + if (!_titleNode) { + _titleNode = [[ASTextNode alloc] init]; + [_titleNode setLayerBacked:YES]; + } + return _titleNode; +} + +- (ASImageNode *)imageNode +{ + if (!_imageNode) { + _imageNode = [[ASImageNode alloc] init]; + [_imageNode setLayerBacked:YES]; + [_titleNode setFlexShrink:YES]; + } + return _imageNode; +} + +- (ASImageNode *)backgroundImageNode +{ + if (!_backgroundImageNode) { + _backgroundImageNode = [[ASImageNode alloc] init]; + [_backgroundImageNode setLayerBacked:YES]; + [_backgroundImageNode setContentMode:UIViewContentModeScaleToFill]; + } + return _backgroundImageNode; +} + - (void)setLayerBacked:(BOOL)layerBacked { ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!"); @@ -105,6 +124,7 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously { [super setDisplaysAsynchronously:displaysAsynchronously]; + [self.backgroundImageNode setDisplaysAsynchronously:displaysAsynchronously]; [self.imageNode setDisplaysAsynchronously:displaysAsynchronously]; [self.titleNode setDisplaysAsynchronously:displaysAsynchronously]; } @@ -124,8 +144,8 @@ newImage = _normalImage; } - if (newImage != self.imageNode.image) { - self.imageNode.image = newImage; + if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { + _imageNode.image = newImage; [self setNeedsLayout]; } } @@ -144,8 +164,8 @@ newTitle = _normalAttributedTitle; } - if (newTitle != self.titleNode.attributedString) { - self.titleNode.attributedString = newTitle; + if ((_titleNode != nil || newTitle.length > 0) && newTitle != self.titleNode.attributedString) { + _titleNode.attributedString = newTitle; [self setNeedsLayout]; } } @@ -165,8 +185,8 @@ newImage = _normalBackgroundImage; } - if (newImage != self.backgroundImageNode.image) { - self.backgroundImageNode.image = newImage; + if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { + _backgroundImageNode.image = newImage; [self setNeedsLayout]; } } @@ -409,12 +429,12 @@ } NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - if (self.imageNode.image) { - [children addObject:self.imageNode]; + if (_imageNode.image) { + [children addObject:_imageNode]; } - if (self.titleNode.attributedString.length > 0) { - [children addObject:self.titleNode]; + if (_titleNode.attributedString.length > 0) { + [children addObject:_titleNode]; } stack.children = children; @@ -425,9 +445,9 @@ spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; } - if (self.backgroundImageNode.image) { + if (_backgroundImageNode.image) { spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec - background:self.backgroundImageNode]; + background:_backgroundImageNode]; } return spec; @@ -436,9 +456,9 @@ - (void)layout { [super layout]; - self.backgroundImageNode.hidden = self.backgroundImageNode.image == nil; - self.imageNode.hidden = self.imageNode.image == nil; - self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO; + _backgroundImageNode.hidden = (_backgroundImageNode.image == nil); + _imageNode.hidden = (_imageNode.image == nil); + _titleNode.hidden = (_titleNode.attributedString.length == 0); } @end From ca8357a364878b20190719edb9c8a6e58c72079d Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 20 Feb 2016 13:01:03 -0800 Subject: [PATCH 166/224] [AS*ImageNode] dispatch to the background before scheduling image download request (perf). --- AsyncDisplayKit/ASMultiplexImageNode.mm | 83 +++++++++++++------------ AsyncDisplayKit/ASNetworkImageNode.mm | 42 +++++++------ 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 5eb0abcfcb..483dfb9973 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -21,6 +21,7 @@ #import "ASLog.h" #import "ASPhotosFrameworkImageRequest.h" #import "ASEqualityHelpers.h" +#import "ASInternalHelpers.h" #if !AS_IOS8_SDK_OR_LATER #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. @@ -751,46 +752,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } // Download! - if (_downloaderSupportsNewProtocol) { - [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL - callbackQueue:dispatch_get_main_queue() - downloadProgress:downloadProgressBlock - completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - ASDN::MutexLocker l(_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock(downloadedImage, error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }]]; - } else { - [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL - callbackQueue:dispatch_get_main_queue() - downloadProgressBlock:downloadProgressBlock - completion:^(CGImageRef coreGraphicsImage, NSError *error) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); - completionBlock(downloadedImage, error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }]]; - } + ASPerformBlockOnBackgroundThread(^{ + if (_downloaderSupportsNewProtocol) { + [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL + callbackQueue:dispatch_get_main_queue() + downloadProgress:downloadProgressBlock + completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) { + // We dereference iVars directly, so we can't have weakSelf going nil on us. + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + ASDN::MutexLocker l(_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + completionBlock(downloadedImage, error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }]]; + } else { + [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL + callbackQueue:dispatch_get_main_queue() + downloadProgressBlock:downloadProgressBlock + completion:^(CGImageRef coreGraphicsImage, NSError *error) { + // We dereference iVars directly, so we can't have weakSelf going nil on us. + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); + completionBlock(downloadedImage, error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }]]; + } + }); } #pragma mark - diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 3b5eb75db9..55263c1051 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -13,6 +13,7 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" #import "ASThread.h" +#import "ASInternalHelpers.h" #if PIN_REMOTE_IMAGE #import "ASPINRemoteImageDownloader.h" @@ -263,25 +264,28 @@ - (void)_downloadImageWithCompletion:(void (^)(UIImage *image, NSError*, id downloadIdentifier))finished { - if (_downloaderSupportsNewProtocol) { - _downloadIdentifier = [_downloader downloadImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(image, error, downloadIdentifier); - } - }]; - } else { - _downloadIdentifier = [_downloader downloadImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - downloadProgressBlock:NULL - completion:^(CGImageRef responseImage, NSError *error) { - if (finished != NULL) { - finished([UIImage imageWithCGImage:responseImage], error, nil); - } - }]; - } + ASPerformBlockOnBackgroundThread(^{ + ASDN::MutexLocker l(_lock); + if (_downloaderSupportsNewProtocol) { + _downloadIdentifier = [_downloader downloadImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(image, error, downloadIdentifier); + } + }]; + } else { + _downloadIdentifier = [_downloader downloadImageWithURL:_URL + callbackQueue:dispatch_get_main_queue() + downloadProgressBlock:NULL + completion:^(CGImageRef responseImage, NSError *error) { + if (finished != NULL) { + finished([UIImage imageWithCGImage:responseImage], error, nil); + } + }]; + } + }); } - (void)_lazilyLoadImageIfNecessary From d899f12f70fe9624db126c3027c4c4cdc795e93f Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 20 Feb 2016 15:24:44 -0800 Subject: [PATCH 167/224] [ASCellNode] Layout delegate should not be public as it must not be reset externally. Do not call layout delegate method before the cell node is loaded. This can happen if application code calls -setNeedsLayout on the cell manually, and can confuse UIKit state because we submit an empty batch update call on the next runloop. --- AsyncDisplayKit/ASCellNode+Internal.h | 24 +++++++++++++++++++++++- AsyncDisplayKit/ASCellNode.h | 18 ------------------ AsyncDisplayKit/ASCellNode.m | 5 +++-- AsyncDisplayKit/ASDisplayNode.mm | 1 - AsyncDisplayKit/ASTableView.mm | 1 + 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index a660e46d80..8dba99cded 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -8,6 +8,28 @@ #import "ASCellNode.h" -@interface ASCellNode (Internal) +@protocol ASCellNodeLayoutDelegate + +/** + * Notifies the delegate that the specified cell node has done a relayout. + * The notification is done on main thread. + * + * This will not be called due to measurement passes before the node has loaded + * its view, even if triggered by -setNeedsLayout, as it is assumed these are + * not relevant to UIKit. Indeed, these calls can cause consistency issues. + * + * @param node A node informing the delegate about the relayout. + * @param sizeChanged `YES` if the node's `calculatedSize` changed during the relayout, `NO` otherwise. + */ +- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; + +@end + +@interface ASCellNode () + +/* + * A delegate to be notified (on main thread) after a relayout. + */ +@property (nonatomic, weak) id layoutDelegate; @end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 8788d30015..5460627a0a 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -14,19 +14,6 @@ NS_ASSUME_NONNULL_BEGIN typedef NSUInteger ASCellNodeAnimation; -@protocol ASCellNodeLayoutDelegate - -/** - * Notifies the delegate that the specified cell node has done a relayout. - * The notification is done on main thread. - * - * @param node A node informing the delegate about the relayout. - * @param sizeChanged `YES` if the node's `calculatedSize` changed during the relayout, `NO` otherwise. - */ -- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; - -@end - /** * Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`. */ @@ -71,11 +58,6 @@ typedef NSUInteger ASCellNodeAnimation; */ @property (nonatomic, assign) BOOL highlighted; -/* - * A delegate to be notified (on main thread) after a relayout. - */ -@property (nonatomic, weak) id layoutDelegate; - /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 3e1fccdbc8..b594cfcce2 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASCellNode.h" +#import "ASCellNode+Internal.h" #import "ASInternalHelpers.h" #import @@ -27,6 +27,7 @@ @end @implementation ASCellNode +@synthesize layoutDelegate = _layoutDelegate; - (instancetype)init { @@ -97,7 +98,7 @@ CGSize oldSize = self.calculatedSize; [super setNeedsLayout]; - if (_layoutDelegate != nil) { + if (_layoutDelegate != nil && self.isNodeLoaded) { BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize); ASPerformBlockOnMainThread(^{ [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0aec6ef564..636db362a2 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1882,7 +1882,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (ASSizeRange)constrainedSizeForCalculatedLayout { - ASDisplayNodeAssertThreadAffinity(self); return _constrainedSize; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 923e690236..c6eefd394b 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -10,6 +10,7 @@ #import "ASAssert.h" #import "ASBatchFetching.h" +#import "ASCellNode+Internal.h" #import "ASChangeSetDataController.h" #import "ASDelegateProxy.h" #import "ASDisplayNode+Beta.h" From 99b674c346a4b538483c38c4940ef8f9ad76d3ea Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 22:40:42 -0800 Subject: [PATCH 168/224] 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 From ea304f7f3721ae012dc12129cff00eb9d16fc029 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 22:46:26 -0800 Subject: [PATCH 169/224] Nodes always read bridged properties from pending state --- .../Private/ASDisplayNode+UIViewBridge.mm | 105 +++++++++--------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 63afebb2ed..6786483f14 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -21,15 +21,15 @@ * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. * In general, a property can either be: * - Always sent to the layer or view's layer - * use _getFromLayer / _setToLayer + * use _getFromPendingViewState / _setToLayer * - Bridged to the view if view-backed or the layer if layer-backed * use _getFromViewOrLayer / _setToViewOrLayer / _messageToViewOrLayer * - Only applicable if view-backed - * use _setToViewOnly / _getFromViewOnly + * use _setToViewOnly / _getFromPendingViewState * - Has differing types on views and layers, or custom ASDisplayNode-specific behavior is desired * manually implement * - * _bridge_prologue is defined to either take an appropriate lock or assert thread affinity. Add it at the beginning of any bridged methods. + * _bridge_prologue is defined to take the node's property lock. Add it at the beginning of any bridged methods. */ #define DISPLAYNODE_USE_LOCKS 1 @@ -37,25 +37,18 @@ #define __loaded (_layer != nil) #if DISPLAYNODE_USE_LOCKS -#define _bridge_prologue ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock) +#define _bridge_prologue ASDN::MutexLocker l(_propertyLock) #else -#define _bridge_prologue ASDisplayNodeAssertThreadAffinity(self) +#define _bridge_prologue () #endif - -#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \ - (_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\ - : self.pendingViewState.viewAndPendingViewStateProperty - #define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? \ (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr))\ : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) #define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) -#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : self.pendingViewState.viewAndPendingViewStateProperty - -#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : self.pendingViewState.layerProperty +#define _getFromPendingViewState(viewAndPendingViewStateProperty) _pendingViewState.viewAndPendingViewStateProperty #define _setToLayer(layerProperty, layerValueExpr) __loaded ? _layer.layerProperty = (layerValueExpr) : self.pendingViewState.layerProperty = (layerValueExpr) @@ -149,7 +142,7 @@ - (CGFloat)alpha { _bridge_prologue; - return _getFromViewOrLayer(opacity, alpha); + return _getFromPendingViewState(alpha); } - (void)setAlpha:(CGFloat)newAlpha @@ -161,7 +154,7 @@ - (CGFloat)cornerRadius { _bridge_prologue; - return _getFromLayer(cornerRadius); + return _getFromPendingViewState(cornerRadius); } - (void)setCornerRadius:(CGFloat)newCornerRadius @@ -173,7 +166,7 @@ - (CGFloat)contentsScale { _bridge_prologue; - return _getFromLayer(contentsScale); + return _getFromPendingViewState(contentsScale); } - (void)setContentsScale:(CGFloat)newContentsScale @@ -185,7 +178,7 @@ - (CGRect)bounds { _bridge_prologue; - return _getFromViewOrLayer(bounds, bounds); + return _getFromPendingViewState(bounds); } - (void)setBounds:(CGRect)newBounds @@ -309,7 +302,7 @@ - (BOOL)isOpaque { _bridge_prologue; - return _getFromLayer(opaque); + return _getFromPendingViewState(opaque); } - (void)setOpaque:(BOOL)newOpaque @@ -317,18 +310,20 @@ BOOL prevOpaque = self.opaque; _bridge_prologue; - _setToLayer(opaque, newOpaque); - if (prevOpaque != newOpaque) { [self setNeedsDisplay]; } + + if (NSThread.isMainThread) { + _setToLayer(opaque, newOpaque); + } } - (BOOL)isUserInteractionEnabled { _bridge_prologue; if (_flags.layerBacked) return NO; - return _getFromViewOnly(userInteractionEnabled); + return _getFromPendingViewState(userInteractionEnabled); } - (void)setUserInteractionEnabled:(BOOL)enabled @@ -340,7 +335,7 @@ - (BOOL)isExclusiveTouch { _bridge_prologue; - return _getFromViewOnly(exclusiveTouch); + return _getFromPendingViewState(exclusiveTouch); } - (void)setExclusiveTouch:(BOOL)exclusiveTouch @@ -352,7 +347,7 @@ - (BOOL)clipsToBounds { _bridge_prologue; - return _getFromViewOrLayer(masksToBounds, clipsToBounds); + return _getFromPendingViewState(clipsToBounds); } - (void)setClipsToBounds:(BOOL)clips @@ -364,7 +359,7 @@ - (CGPoint)anchorPoint { _bridge_prologue; - return _getFromLayer(anchorPoint); + return _getFromPendingViewState(anchorPoint); } - (void)setAnchorPoint:(CGPoint)newAnchorPoint @@ -376,7 +371,7 @@ - (CGPoint)position { _bridge_prologue; - return _getFromLayer(position); + return _getFromPendingViewState(position); } - (void)setPosition:(CGPoint)newPosition @@ -388,7 +383,7 @@ - (CGFloat)zPosition { _bridge_prologue; - return _getFromLayer(zPosition); + return _getFromPendingViewState(zPosition); } - (void)setZPosition:(CGFloat)newPosition @@ -400,7 +395,7 @@ - (CATransform3D)transform { _bridge_prologue; - return _getFromLayer(transform); + return _getFromPendingViewState(transform); } - (void)setTransform:(CATransform3D)newTransform @@ -412,7 +407,7 @@ - (CATransform3D)subnodeTransform { _bridge_prologue; - return _getFromLayer(sublayerTransform); + return _getFromPendingViewState(sublayerTransform); } - (void)setSubnodeTransform:(CATransform3D)newSubnodeTransform @@ -424,7 +419,7 @@ - (id)contents { _bridge_prologue; - return _getFromLayer(contents); + return _getFromPendingViewState(contents); } - (void)setContents:(id)newContents @@ -436,7 +431,7 @@ - (BOOL)isHidden { _bridge_prologue; - return _getFromViewOrLayer(hidden, hidden); + return _getFromPendingViewState(hidden); } - (void)setHidden:(BOOL)flag @@ -448,7 +443,7 @@ - (BOOL)needsDisplayOnBoundsChange { _bridge_prologue; - return _getFromLayer(needsDisplayOnBoundsChange); + return _getFromPendingViewState(needsDisplayOnBoundsChange); } - (void)setNeedsDisplayOnBoundsChange:(BOOL)flag @@ -461,7 +456,7 @@ { _bridge_prologue; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(autoresizesSubviews); + return _getFromPendingViewState(autoresizesSubviews); } - (void)setAutoresizesSubviews:(BOOL)flag @@ -475,7 +470,7 @@ { _bridge_prologue; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(autoresizingMask); + return _getFromPendingViewState(autoresizingMask); } - (void)setAutoresizingMask:(UIViewAutoresizing)mask @@ -516,7 +511,7 @@ - (UIColor *)backgroundColor { _bridge_prologue; - return [UIColor colorWithCGColor:_getFromLayer(backgroundColor)]; + return [UIColor colorWithCGColor:_getFromPendingViewState(backgroundColor)]; } - (void)setBackgroundColor:(UIColor *)newBackgroundColor @@ -536,7 +531,7 @@ { _bridge_prologue; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromViewOnly(tintColor); + return _getFromPendingViewState(tintColor); } - (void)setTintColor:(UIColor *)color @@ -554,7 +549,7 @@ - (CGColorRef)shadowColor { _bridge_prologue; - return _getFromLayer(shadowColor); + return _getFromPendingViewState(shadowColor); } - (void)setShadowColor:(CGColorRef)colorValue @@ -566,7 +561,7 @@ - (CGFloat)shadowOpacity { _bridge_prologue; - return _getFromLayer(shadowOpacity); + return _getFromPendingViewState(shadowOpacity); } - (void)setShadowOpacity:(CGFloat)opacity @@ -578,7 +573,7 @@ - (CGSize)shadowOffset { _bridge_prologue; - return _getFromLayer(shadowOffset); + return _getFromPendingViewState(shadowOffset); } - (void)setShadowOffset:(CGSize)offset @@ -590,7 +585,7 @@ - (CGFloat)shadowRadius { _bridge_prologue; - return _getFromLayer(shadowRadius); + return _getFromPendingViewState(shadowRadius); } - (void)setShadowRadius:(CGFloat)radius @@ -602,7 +597,7 @@ - (CGFloat)borderWidth { _bridge_prologue; - return _getFromLayer(borderWidth); + return _getFromPendingViewState(borderWidth); } - (void)setBorderWidth:(CGFloat)width @@ -614,7 +609,7 @@ - (CGColorRef)borderColor { _bridge_prologue; - return _getFromLayer(borderColor); + return _getFromPendingViewState(borderColor); } - (void)setBorderColor:(CGColorRef)colorValue @@ -626,7 +621,7 @@ - (BOOL)allowsEdgeAntialiasing { _bridge_prologue; - return _getFromLayer(allowsEdgeAntialiasing); + return _getFromPendingViewState(allowsEdgeAntialiasing); } - (void)setAllowsEdgeAntialiasing:(BOOL)allowsEdgeAntialiasing @@ -638,7 +633,7 @@ - (unsigned int)edgeAntialiasingMask { _bridge_prologue; - return _getFromLayer(edgeAntialiasingMask); + return _getFromPendingViewState(edgeAntialiasingMask); } - (void)setEdgeAntialiasingMask:(unsigned int)edgeAntialiasingMask @@ -650,7 +645,7 @@ - (BOOL)isAccessibilityElement { _bridge_prologue; - return _getFromViewOnly(isAccessibilityElement); + return _getFromPendingViewState(isAccessibilityElement); } - (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement @@ -662,7 +657,7 @@ - (NSString *)accessibilityLabel { _bridge_prologue; - return _getFromViewOnly(accessibilityLabel); + return _getFromPendingViewState(accessibilityLabel); } - (void)setAccessibilityLabel:(NSString *)accessibilityLabel @@ -674,7 +669,7 @@ - (NSString *)accessibilityHint { _bridge_prologue; - return _getFromViewOnly(accessibilityHint); + return _getFromPendingViewState(accessibilityHint); } - (void)setAccessibilityHint:(NSString *)accessibilityHint @@ -686,7 +681,7 @@ - (NSString *)accessibilityValue { _bridge_prologue; - return _getFromViewOnly(accessibilityValue); + return _getFromPendingViewState(accessibilityValue); } - (void)setAccessibilityValue:(NSString *)accessibilityValue @@ -698,7 +693,7 @@ - (UIAccessibilityTraits)accessibilityTraits { _bridge_prologue; - return _getFromViewOnly(accessibilityTraits); + return _getFromPendingViewState(accessibilityTraits); } - (void)setAccessibilityTraits:(UIAccessibilityTraits)accessibilityTraits @@ -710,7 +705,7 @@ - (CGRect)accessibilityFrame { _bridge_prologue; - return _getFromViewOnly(accessibilityFrame); + return _getFromPendingViewState(accessibilityFrame); } - (void)setAccessibilityFrame:(CGRect)accessibilityFrame @@ -722,7 +717,7 @@ - (NSString *)accessibilityLanguage { _bridge_prologue; - return _getFromViewOnly(accessibilityLanguage); + return _getFromPendingViewState(accessibilityLanguage); } - (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage @@ -734,7 +729,7 @@ - (BOOL)accessibilityElementsHidden { _bridge_prologue; - return _getFromViewOnly(accessibilityElementsHidden); + return _getFromPendingViewState(accessibilityElementsHidden); } - (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden @@ -746,7 +741,7 @@ - (BOOL)accessibilityViewIsModal { _bridge_prologue; - return _getFromViewOnly(accessibilityViewIsModal); + return _getFromPendingViewState(accessibilityViewIsModal); } - (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal @@ -758,7 +753,7 @@ - (BOOL)shouldGroupAccessibilityChildren { _bridge_prologue; - return _getFromViewOnly(shouldGroupAccessibilityChildren); + return _getFromPendingViewState(shouldGroupAccessibilityChildren); } - (void)setShouldGroupAccessibilityChildren:(BOOL)shouldGroupAccessibilityChildren @@ -770,7 +765,7 @@ - (NSString *)accessibilityIdentifier { _bridge_prologue; - return _getFromViewOnly(accessibilityIdentifier); + return _getFromPendingViewState(accessibilityIdentifier); } - (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier @@ -787,7 +782,7 @@ - (BOOL)asyncdisplaykit_isAsyncTransactionContainer { _bridge_prologue; - return _getFromViewOrLayer(asyncdisplaykit_isAsyncTransactionContainer, asyncdisplaykit_isAsyncTransactionContainer); + return _getFromPendingViewState(asyncdisplaykit_isAsyncTransactionContainer); } - (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)asyncTransactionContainer From b5b5f9f559fa4cd41fc6b78f5f5c9d12015c58ac Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:39:11 -0800 Subject: [PATCH 170/224] Some clean up --- AsyncDisplayKit/ASDisplayNode.mm | 10 +----- .../Private/ASDisplayNode+UIViewBridge.mm | 34 ++++++++++++------- .../Private/ASDisplayNodeInternal.h | 5 +-- .../Private/ASPendingStateController.mm | 2 ++ AsyncDisplayKitTests/ASDisplayNodeTests.m | 14 ++++---- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a045f30b28..fce8cd2198 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -277,6 +277,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _contentsScaleForDisplay = ASScreenScale(); _displaySentinel = [[ASSentinel alloc] init]; _preferredFrameSize = CGSizeZero; + _pendingViewState = [_ASPendingState new]; } - (id)init @@ -2358,15 +2359,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) #pragma mark - Pending View State -- (_ASPendingState *)pendingViewState -{ - if (!_pendingViewState) { - _pendingViewState = [[_ASPendingState alloc] init]; - ASDisplayNodeAssertNotNil(_pendingViewState, @"should have created a pendingViewState"); - } - - return _pendingViewState; -} - (void)_applyPendingStateToViewOrLayer { diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 6786483f14..d24b7d2066 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -16,6 +16,7 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" #import "ASEqualityHelpers.h" +#import "ASPendingStateController.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -42,11 +43,25 @@ #define _bridge_prologue () #endif -#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? \ - (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr))\ - : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) +/// Returns YES if the property set should be applied to view/layer immediately. +ASDISPLAYNODE_INLINE BOOL ASDisplayNodeMarkDirtyIfNeeded(ASDisplayNode *node) { + if (NSThread.isMainThread) { + return node.nodeLoaded; + } else { + if (node.nodeLoaded && !node->_pendingViewState.hasChanges) { + [ASPendingStateController.sharedInstance registerNode:node]; + } + return NO; + } +}; -#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) __loaded ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : self.pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) +#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ + _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ + if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } + +#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ +_pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ +if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } #define _getFromPendingViewState(viewAndPendingViewStateProperty) _pendingViewState.viewAndPendingViewStateProperty @@ -217,7 +232,6 @@ // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif - _setToViewOnly(frame, rect); } else { // This is by far the common case / hot path. @@ -307,16 +321,10 @@ - (void)setOpaque:(BOOL)newOpaque { - BOOL prevOpaque = self.opaque; - _bridge_prologue; - if (prevOpaque != newOpaque) { - [self setNeedsDisplay]; - } + _setToViewOrLayer(opaque, newOpaque, opaque, newOpaque); - if (NSThread.isMainThread) { - _setToLayer(opaque, newOpaque); - } + // TODO: Mark as needs display if value changed? } - (BOOL)isUserInteractionEnabled diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 147323a61b..498b0ff0cc 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -50,6 +50,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @interface ASDisplayNode () { +@package + _ASPendingState *_pendingViewState; + @protected // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. ASDN::RecursiveMutex _propertyLock; @@ -92,8 +95,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // keeps track of nodes/subnodes that have not finished display, used with placeholders NSMutableSet *_pendingDisplayNodes; - - _ASPendingState *_pendingViewState; struct ASDisplayNodeFlags { // public properties diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 26141ae2e3..f7d8f6e52c 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -10,6 +10,7 @@ #import "ASThread.h" #import "ASWeakSet.h" #import "ASDisplayNode.h" +#import "ASAssert.h" @interface ASPendingStateController() { @@ -56,6 +57,7 @@ - (void)registerNode:(ASDisplayNode *)node { + ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node); ASDN::MutexLocker l(_lock); [_dirtyNodes addObject:node]; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 455591cd1b..e86c10484e 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -425,6 +425,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\ - (void)checkSimpleBridgePropertiesSetPropagate:(BOOL)isLayerBacked { + /// The first node we instantiate must be created on the main thread + /// in order to read ASScreenScale() safely. We create this throwaway + /// node so that running this test first in the suite + /// doesn't cause a deadlock. + [ASDisplayNode new]; + __block ASDisplayNode *node = nil; [self executeOffThread:^{ @@ -484,13 +490,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ [self checkValuesMatchSetValues:node isLayerBacked:isLayerBacked]; - // As a final sanity check, change a value on the realized view and ensure it is fetched through the node. - if (isLayerBacked) { - node.layer.hidden = NO; - } else { - node.view.hidden = NO; - } - XCTAssertEqual(NO, node.hidden, @"After the view is realized, the node should delegate properties to the view."); + // TODO: Handle backwards propagation i.e. from view/layer to node. } // Set each of the simple bridged UIView properties to a non-default value off-thread, then From c9d53517fad051b6ad6989048d006bd7b86e2e82 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:48:10 -0800 Subject: [PATCH 171/224] Lock node's properties while pending view state is applied --- AsyncDisplayKit/ASDisplayNode.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fce8cd2198..0b2f6de2b0 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -985,6 +985,7 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (void)applyPendingViewState { ASDisplayNodeAssertMainThread(); + ASDN::MutexLocker l(_propertyLock); if (self.layerBacked) { [_pendingViewState applyToLayer:self.layer]; } else { From 97d73cbbc3eff6442d59c5ba96e52bf93439f4b8 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:50:43 -0800 Subject: [PATCH 172/224] Actually do the thing --- AsyncDisplayKit/Private/ASPendingStateController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index f7d8f6e52c..19c6d51137 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -9,8 +9,8 @@ #import "ASPendingStateController.h" #import "ASThread.h" #import "ASWeakSet.h" -#import "ASDisplayNode.h" #import "ASAssert.h" +#import "ASDisplayNodeInternal.h" @interface ASPendingStateController() { @@ -83,7 +83,7 @@ { ASDN::MutexLocker l(_lock); for (__unused ASDisplayNode *node in _dirtyNodes) { - // TODO: apply pending state. + [node applyPendingViewState]; } [_dirtyNodes removeAllObjects]; _flags.pendingFlush = NO; From 391ce154976f15facf70d67ebcd60b00cb5a59ac Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:51:17 -0800 Subject: [PATCH 173/224] Remove gunk from Cocoapods --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3a116b914d..4b3e970c23 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1637,7 +1637,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - 78B4649EC16385BFAFD84D49 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1767,21 +1766,6 @@ 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 */ From 3d22b18bcf4faf2bc197223a88905263c716530a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:56:26 -0800 Subject: [PATCH 174/224] Finish that thought. --- AsyncDisplayKit/Private/ASPendingStateController.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Private/ASPendingStateController.h b/AsyncDisplayKit/Private/ASPendingStateController.h index c67209030f..fc82817909 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.h +++ b/AsyncDisplayKit/Private/ASPendingStateController.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN have been set on background threads. This controller will enqueue run-loop events to flush changes - but if you need + but if you need them flushed now you can call `flush` from the main thread. */ @interface ASPendingStateController : NSObject @@ -36,7 +36,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)flush; /** - Register this node as having pending state that needs + Register this node as having pending state that needs to be copied + over to the view/layer. This is called automatically by display nodes + when their view/layer properties are set post-load on background threads. */ - (void)registerNode:(ASDisplayNode *)node; From 0c6ad25f4630be68400587208dce1913d1731bce Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jan 2016 23:59:55 -0800 Subject: [PATCH 175/224] Add some documentation --- AsyncDisplayKit/Private/ASWeakSet.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m index 1f41a9846b..586fb61691 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.m +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -51,6 +51,14 @@ return YES; } +/** + Note: The `count` property of NSMapTable is unreliable + in the case of weak-to-strong map tables because entries + whose keys have been deallocated are not removed immediately. + + In order to get the true count we have to fall back to using + fast enumeration. + */ - (NSUInteger)count { NSInteger count = 0; From 70bc80a304028cf25542849b5e34619608db8768 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 8 Jan 2016 00:00:34 -0800 Subject: [PATCH 176/224] Fix map table type in ASWeakSet --- AsyncDisplayKit/Private/ASWeakSet.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m index 586fb61691..6311808c25 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.m +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -9,7 +9,7 @@ #import "ASWeakSet.h" @interface ASWeakSet<__covariant ObjectType> () -@property (nonatomic, strong, readonly) NSMapTable *mapTable; +@property (nonatomic, strong, readonly) NSMapTable *mapTable; @end @implementation ASWeakSet From 55e3f1ee007957b28b99eca775a3dcdcaea01dcb Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 8 Jan 2016 00:02:53 -0800 Subject: [PATCH 177/224] Fix a stupid type mismatch in ASWeakSet --- AsyncDisplayKit/Private/ASWeakSet.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m index 6311808c25..9ac3020e74 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.m +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -61,7 +61,7 @@ */ - (NSUInteger)count { - NSInteger count = 0; + NSUInteger count = 0; for (__unused id object in _mapTable) { count += 1; } From 9136ecc77b4ff2bd8b42c9c7b814ec13567e4c32 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 8 Jan 2016 00:33:15 -0800 Subject: [PATCH 178/224] The node is used now --- AsyncDisplayKit/Private/ASPendingStateController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 19c6d51137..04bdcf7953 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -82,7 +82,7 @@ - (void)flushNow { ASDN::MutexLocker l(_lock); - for (__unused ASDisplayNode *node in _dirtyNodes) { + for (ASDisplayNode *node in _dirtyNodes) { [node applyPendingViewState]; } [_dirtyNodes removeAllObjects]; From 07b4addf1ab92652e66b7f584fb95e3fa7525805 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 8 Jan 2016 00:46:25 -0800 Subject: [PATCH 179/224] Document re-entrancy hazard in ASPendingStateController --- AsyncDisplayKit/Private/ASPendingStateController.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 04bdcf7953..58d20068ba 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -79,6 +79,13 @@ [self performSelectorOnMainThread:@selector(flushNow) withObject:nil waitUntilDone:NO modes:@[ NSRunLoopCommonModes ]]; } +/** + * NOTE: There is a small re-entrancy hazard here. + * If the user gives us a subclass of UIView/CALayer that + * adds side-effects to property sets, and one side effect + * waits on a background thread that sets a view/layer property + * on a loaded node, then we've got a deadlock. + */ - (void)flushNow { ASDN::MutexLocker l(_lock); From 1514cef36df1528f750f674e8a5c122fc595b890 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Feb 2016 17:28:02 -0800 Subject: [PATCH 180/224] Beef up the unit tests and make em pass --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +- .../Private/ASDisplayNode+UIViewBridge.mm | 303 +++++++++--------- .../ASBridgedPropertiesTests.mm | 107 +++++++ .../ASPendingStateControllerTests.m | 40 --- 4 files changed, 268 insertions(+), 190 deletions(-) create mode 100644 AsyncDisplayKitTests/ASBridgedPropertiesTests.mm delete mode 100644 AsyncDisplayKitTests/ASPendingStateControllerTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4b3e970c23..09d20de456 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -464,7 +464,7 @@ 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 */; }; + CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; }; 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 */; }; @@ -801,7 +801,7 @@ 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 = ""; }; + CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; 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 = ""; }; @@ -1018,7 +1018,7 @@ children = ( DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, - CC3B208F1C3F892D00798563 /* ASPendingStateControllerTests.m */, + CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */, CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, @@ -1912,7 +1912,7 @@ AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, - CC3B20901C3F892D00798563 /* ASPendingStateControllerTests.m in Sources */, + CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index d24b7d2066..f33f56ca20 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -17,20 +17,22 @@ #import "ASDisplayNode+Beta.h" #import "ASEqualityHelpers.h" #import "ASPendingStateController.h" +#import "ASThread.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. * In general, a property can either be: * - Always sent to the layer or view's layer - * use _getFromPendingViewState / _setToLayer + * use _getFromLayer / _setToLayer * - Bridged to the view if view-backed or the layer if layer-backed * use _getFromViewOrLayer / _setToViewOrLayer / _messageToViewOrLayer * - Only applicable if view-backed - * use _setToViewOnly / _getFromPendingViewState + * use _setToViewOnly / _getFromViewOnly * - Has differing types on views and layers, or custom ASDisplayNode-specific behavior is desired * manually implement * - * _bridge_prologue is defined to take the node's property lock. Add it at the beginning of any bridged methods. + * _bridge_prologue_write is defined to take the node's property lock. Add it at the beginning of any bridged property setters. + * _bridge_prologue_read is defined to take the node's property lock and enforce thread affinity. Add it at the beginning of any bridged property getters. */ #define DISPLAYNODE_USE_LOCKS 1 @@ -38,14 +40,19 @@ #define __loaded (_layer != nil) #if DISPLAYNODE_USE_LOCKS -#define _bridge_prologue ASDN::MutexLocker l(_propertyLock) +#define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self) +#define _bridge_prologue_write ASDN::MutexLocker l(_propertyLock) #else -#define _bridge_prologue () +#define _bridge_prologue_read ASDisplayNodeAssertThreadAffinity(self) +#define _bridge_prologue_write #endif /// Returns YES if the property set should be applied to view/layer immediately. -ASDISPLAYNODE_INLINE BOOL ASDisplayNodeMarkDirtyIfNeeded(ASDisplayNode *node) { - if (NSThread.isMainThread) { +/// Side Effect: Registers the node with the shared ASPendingStateController if +/// the property cannot be immediately applied and the node does not already have pending changes. +/// This function must be called with the node's lock already held (after _bridge_prologue_write). +ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) { + if (ASDisplayNodeThreadIsMain()) { return node.nodeLoaded; } else { if (node.nodeLoaded && !node->_pendingViewState.hasChanges) { @@ -55,15 +62,19 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeMarkDirtyIfNeeded(ASDisplayNode *node) { } }; -#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ - _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ - if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } +#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \ + (_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\ + : self.pendingViewState.viewAndPendingViewStateProperty -#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeMarkDirtyIfNeeded(self); \ -_pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); \ -if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } +#define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ + if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } -#define _getFromPendingViewState(viewAndPendingViewStateProperty) _pendingViewState.viewAndPendingViewStateProperty +#define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ +if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } + +#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : self.pendingViewState.viewAndPendingViewStateProperty + +#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : self.pendingViewState.layerProperty #define _setToLayer(layerProperty, layerValueExpr) __loaded ? _layer.layerProperty = (layerValueExpr) : self.pendingViewState.layerProperty = (layerValueExpr) @@ -156,55 +167,55 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (CGFloat)alpha { - _bridge_prologue; - return _getFromPendingViewState(alpha); + _bridge_prologue_read; + return _getFromViewOrLayer(opacity, alpha); } - (void)setAlpha:(CGFloat)newAlpha { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(opacity, newAlpha, alpha, newAlpha); } - (CGFloat)cornerRadius { - _bridge_prologue; - return _getFromPendingViewState(cornerRadius); + _bridge_prologue_read; + return _getFromLayer(cornerRadius); } - (void)setCornerRadius:(CGFloat)newCornerRadius { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(cornerRadius, newCornerRadius); } - (CGFloat)contentsScale { - _bridge_prologue; - return _getFromPendingViewState(contentsScale); + _bridge_prologue_read; + return _getFromLayer(contentsScale); } - (void)setContentsScale:(CGFloat)newContentsScale { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(contentsScale, newContentsScale); } - (CGRect)bounds { - _bridge_prologue; - return _getFromPendingViewState(bounds); + _bridge_prologue_read; + return _getFromViewOrLayer(bounds, bounds); } - (void)setBounds:(CGRect)newBounds { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(bounds, newBounds, bounds, newBounds); } - (CGRect)frame { - _bridge_prologue; + _bridge_prologue_read; // Frame is only defined when transform is identity. #if DEBUG @@ -222,7 +233,7 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setFrame:(CGRect)rect { - _bridge_prologue; + _bridge_prologue_write; if (_flags.synchronous && !_flags.layerBacked) { // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: @@ -247,9 +258,8 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt */ - (void)__setSafeFrame:(CGRect)rect { - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); - + _bridge_prologue_write; + BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin); @@ -270,7 +280,7 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setNeedsDisplay { - _bridge_prologue; + _bridge_prologue_write; if (_hierarchyState & ASHierarchyStateRasterized) { ASPerformBlockOnMainThread(^{ @@ -308,20 +318,21 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setNeedsLayout { - _bridge_prologue; + _bridge_prologue_write; + // FIXME: This method currently asserts on the node's thread affinity. [self __setNeedsLayout]; _messageToViewOrLayer(setNeedsLayout); } - (BOOL)isOpaque { - _bridge_prologue; - return _getFromPendingViewState(opaque); + _bridge_prologue_read; + return _getFromLayer(opaque); } - (void)setOpaque:(BOOL)newOpaque { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(opaque, newOpaque, opaque, newOpaque); // TODO: Mark as needs display if value changed? @@ -329,168 +340,168 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (BOOL)isUserInteractionEnabled { - _bridge_prologue; + _bridge_prologue_read; if (_flags.layerBacked) return NO; - return _getFromPendingViewState(userInteractionEnabled); + return _getFromViewOnly(userInteractionEnabled); } - (void)setUserInteractionEnabled:(BOOL)enabled { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(userInteractionEnabled, enabled); } #if TARGET_OS_IOS - (BOOL)isExclusiveTouch { - _bridge_prologue; - return _getFromPendingViewState(exclusiveTouch); + _bridge_prologue_read; + return _getFromViewOnly(exclusiveTouch); } - (void)setExclusiveTouch:(BOOL)exclusiveTouch { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(exclusiveTouch, exclusiveTouch); } #endif - (BOOL)clipsToBounds { - _bridge_prologue; - return _getFromPendingViewState(clipsToBounds); + _bridge_prologue_read; + return _getFromViewOrLayer(masksToBounds, clipsToBounds); } - (void)setClipsToBounds:(BOOL)clips { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(masksToBounds, clips, clipsToBounds, clips); } - (CGPoint)anchorPoint { - _bridge_prologue; - return _getFromPendingViewState(anchorPoint); + _bridge_prologue_read; + return _getFromLayer(anchorPoint); } - (void)setAnchorPoint:(CGPoint)newAnchorPoint { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(anchorPoint, newAnchorPoint); } - (CGPoint)position { - _bridge_prologue; - return _getFromPendingViewState(position); + _bridge_prologue_read; + return _getFromLayer(position); } - (void)setPosition:(CGPoint)newPosition { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(position, newPosition); } - (CGFloat)zPosition { - _bridge_prologue; - return _getFromPendingViewState(zPosition); + _bridge_prologue_read; + return _getFromLayer(zPosition); } - (void)setZPosition:(CGFloat)newPosition { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(zPosition, newPosition); } - (CATransform3D)transform { - _bridge_prologue; - return _getFromPendingViewState(transform); + _bridge_prologue_read; + return _getFromLayer(transform); } - (void)setTransform:(CATransform3D)newTransform { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(transform, newTransform); } - (CATransform3D)subnodeTransform { - _bridge_prologue; - return _getFromPendingViewState(sublayerTransform); + _bridge_prologue_read; + return _getFromLayer(sublayerTransform); } - (void)setSubnodeTransform:(CATransform3D)newSubnodeTransform { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(sublayerTransform, newSubnodeTransform); } - (id)contents { - _bridge_prologue; - return _getFromPendingViewState(contents); + _bridge_prologue_read; + return _getFromLayer(contents); } - (void)setContents:(id)newContents { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(contents, newContents); } - (BOOL)isHidden { - _bridge_prologue; - return _getFromPendingViewState(hidden); + _bridge_prologue_read; + return _getFromViewOrLayer(hidden, hidden); } - (void)setHidden:(BOOL)flag { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(hidden, flag, hidden, flag); } - (BOOL)needsDisplayOnBoundsChange { - _bridge_prologue; - return _getFromPendingViewState(needsDisplayOnBoundsChange); + _bridge_prologue_read; + return _getFromLayer(needsDisplayOnBoundsChange); } - (void)setNeedsDisplayOnBoundsChange:(BOOL)flag { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(needsDisplayOnBoundsChange, flag); } - (BOOL)autoresizesSubviews { - _bridge_prologue; + _bridge_prologue_read; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromPendingViewState(autoresizesSubviews); + return _getFromViewOnly(autoresizesSubviews); } - (void)setAutoresizesSubviews:(BOOL)flag { - _bridge_prologue; + _bridge_prologue_write; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); _setToViewOnly(autoresizesSubviews, flag); } - (UIViewAutoresizing)autoresizingMask { - _bridge_prologue; + _bridge_prologue_read; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromPendingViewState(autoresizingMask); + return _getFromViewOnly(autoresizingMask); } - (void)setAutoresizingMask:(UIViewAutoresizing)mask { - _bridge_prologue; + _bridge_prologue_write; ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); _setToViewOnly(autoresizingMask, mask); } - (UIViewContentMode)contentMode { - _bridge_prologue; + _bridge_prologue_read; if (__loaded) { if (_flags.layerBacked) { return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); @@ -504,7 +515,7 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setContentMode:(UIViewContentMode)contentMode { - _bridge_prologue; + _bridge_prologue_write; if (__loaded) { if (_flags.layerBacked) { _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); @@ -518,15 +529,15 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (UIColor *)backgroundColor { - _bridge_prologue; - return [UIColor colorWithCGColor:_getFromPendingViewState(backgroundColor)]; + _bridge_prologue_read; + return [UIColor colorWithCGColor:_getFromLayer(backgroundColor)]; } - (void)setBackgroundColor:(UIColor *)newBackgroundColor { UIColor *prevBackgroundColor = self.backgroundColor; - _bridge_prologue; + _bridge_prologue_write; _setToLayer(backgroundColor, newBackgroundColor.CGColor); // Note: This check assumes that the colors are within the same color space. @@ -537,16 +548,16 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (UIColor *)tintColor { - _bridge_prologue; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - return _getFromPendingViewState(tintColor); + _bridge_prologue_read; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + return _getFromViewOnly(tintColor); } - (void)setTintColor:(UIColor *)color { - _bridge_prologue; - ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); - _setToViewOnly(tintColor, color); + _bridge_prologue_write; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + _setToViewOnly(tintColor, color); } - (void)tintColorDidChange @@ -556,229 +567,229 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (CGColorRef)shadowColor { - _bridge_prologue; - return _getFromPendingViewState(shadowColor); + _bridge_prologue_read; + return _getFromLayer(shadowColor); } - (void)setShadowColor:(CGColorRef)colorValue { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(shadowColor, colorValue); } - (CGFloat)shadowOpacity { - _bridge_prologue; - return _getFromPendingViewState(shadowOpacity); + _bridge_prologue_read; + return _getFromLayer(shadowOpacity); } - (void)setShadowOpacity:(CGFloat)opacity { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(shadowOpacity, opacity); } - (CGSize)shadowOffset { - _bridge_prologue; - return _getFromPendingViewState(shadowOffset); + _bridge_prologue_read; + return _getFromLayer(shadowOffset); } - (void)setShadowOffset:(CGSize)offset { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(shadowOffset, offset); } - (CGFloat)shadowRadius { - _bridge_prologue; - return _getFromPendingViewState(shadowRadius); + _bridge_prologue_read; + return _getFromLayer(shadowRadius); } - (void)setShadowRadius:(CGFloat)radius { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(shadowRadius, radius); } - (CGFloat)borderWidth { - _bridge_prologue; - return _getFromPendingViewState(borderWidth); + _bridge_prologue_read; + return _getFromLayer(borderWidth); } - (void)setBorderWidth:(CGFloat)width { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(borderWidth, width); } - (CGColorRef)borderColor { - _bridge_prologue; - return _getFromPendingViewState(borderColor); + _bridge_prologue_read; + return _getFromLayer(borderColor); } - (void)setBorderColor:(CGColorRef)colorValue { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(borderColor, colorValue); } - (BOOL)allowsEdgeAntialiasing { - _bridge_prologue; - return _getFromPendingViewState(allowsEdgeAntialiasing); + _bridge_prologue_read; + return _getFromLayer(allowsEdgeAntialiasing); } - (void)setAllowsEdgeAntialiasing:(BOOL)allowsEdgeAntialiasing { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(allowsEdgeAntialiasing, allowsEdgeAntialiasing); } - (unsigned int)edgeAntialiasingMask { - _bridge_prologue; - return _getFromPendingViewState(edgeAntialiasingMask); + _bridge_prologue_read; + return _getFromLayer(edgeAntialiasingMask); } - (void)setEdgeAntialiasingMask:(unsigned int)edgeAntialiasingMask { - _bridge_prologue; + _bridge_prologue_write; _setToLayer(edgeAntialiasingMask, edgeAntialiasingMask); } - (BOOL)isAccessibilityElement { - _bridge_prologue; - return _getFromPendingViewState(isAccessibilityElement); + _bridge_prologue_read; + return _getFromViewOnly(isAccessibilityElement); } - (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(isAccessibilityElement, isAccessibilityElement); } - (NSString *)accessibilityLabel { - _bridge_prologue; - return _getFromPendingViewState(accessibilityLabel); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityLabel); } - (void)setAccessibilityLabel:(NSString *)accessibilityLabel { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityLabel, accessibilityLabel); } - (NSString *)accessibilityHint { - _bridge_prologue; - return _getFromPendingViewState(accessibilityHint); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityHint); } - (void)setAccessibilityHint:(NSString *)accessibilityHint { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityHint, accessibilityHint); } - (NSString *)accessibilityValue { - _bridge_prologue; - return _getFromPendingViewState(accessibilityValue); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityValue); } - (void)setAccessibilityValue:(NSString *)accessibilityValue { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityValue, accessibilityValue); } - (UIAccessibilityTraits)accessibilityTraits { - _bridge_prologue; - return _getFromPendingViewState(accessibilityTraits); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityTraits); } - (void)setAccessibilityTraits:(UIAccessibilityTraits)accessibilityTraits { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityTraits, accessibilityTraits); } - (CGRect)accessibilityFrame { - _bridge_prologue; - return _getFromPendingViewState(accessibilityFrame); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityFrame); } - (void)setAccessibilityFrame:(CGRect)accessibilityFrame { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityFrame, accessibilityFrame); } - (NSString *)accessibilityLanguage { - _bridge_prologue; - return _getFromPendingViewState(accessibilityLanguage); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityLanguage); } - (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityLanguage, accessibilityLanguage); } - (BOOL)accessibilityElementsHidden { - _bridge_prologue; - return _getFromPendingViewState(accessibilityElementsHidden); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityElementsHidden); } - (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityElementsHidden, accessibilityElementsHidden); } - (BOOL)accessibilityViewIsModal { - _bridge_prologue; - return _getFromPendingViewState(accessibilityViewIsModal); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityViewIsModal); } - (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityViewIsModal, accessibilityViewIsModal); } - (BOOL)shouldGroupAccessibilityChildren { - _bridge_prologue; - return _getFromPendingViewState(shouldGroupAccessibilityChildren); + _bridge_prologue_read; + return _getFromViewOnly(shouldGroupAccessibilityChildren); } - (void)setShouldGroupAccessibilityChildren:(BOOL)shouldGroupAccessibilityChildren { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren); } - (NSString *)accessibilityIdentifier { - _bridge_prologue; - return _getFromPendingViewState(accessibilityIdentifier); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityIdentifier); } - (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - _bridge_prologue; + _bridge_prologue_write; _setToViewOnly(accessibilityIdentifier, accessibilityIdentifier); } @@ -789,13 +800,13 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (BOOL)asyncdisplaykit_isAsyncTransactionContainer { - _bridge_prologue; - return _getFromPendingViewState(asyncdisplaykit_isAsyncTransactionContainer); + _bridge_prologue_read; + return _getFromViewOrLayer(asyncdisplaykit_isAsyncTransactionContainer, asyncdisplaykit_isAsyncTransactionContainer); } - (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)asyncTransactionContainer { - _bridge_prologue; + _bridge_prologue_write; _setToViewOrLayer(asyncdisplaykit_asyncTransactionContainer, asyncTransactionContainer, asyncdisplaykit_asyncTransactionContainer, asyncTransactionContainer); } diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm new file mode 100644 index 0000000000..ae80fd81ff --- /dev/null +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -0,0 +1,107 @@ +// +// ASBridgedPropertiesTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 1/7/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "ASPendingStateController.h" +#import "ASDisplayNode.h" +#import "ASThread.h" +#import "ASDisplayNodeInternal.h" +#import "_ASPendingState.h" + +@interface ASPendingStateController (Testing) +- (BOOL)test_isFlushScheduled; +@end + +@interface ASBridgedPropertiesTests : XCTestCase + +@end + +/// Dispatches the given block synchronously onto a different thread. +/// This is useful for testing non-main-thread behavior because `dispatch_sync` +/// will often use the current thread. +static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(q, ^{ + ASDisplayNodeCAssertNotMainThread(); + block(); + dispatch_semaphore_signal(sem); + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); +} + +@implementation ASBridgedPropertiesTests + +- (void)testTheresASharedInstance +{ + XCTAssertNotNil([ASPendingStateController sharedInstance]); +} + +- (void)testThatSettingABridgedPropertyInBackgroundGetsFlushedOnNextRunLoop +{ + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + XCTAssertEqual(node.alpha, 1); + ASDispatchSyncOnOtherThread(^{ + node.alpha = 0; + }); + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + XCTAssertEqual(node.alpha, 0); +} + +- (void)testThatReadingABridgedPropertyInBackgroundThrowsAnException +{ + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + ASDispatchSyncOnOtherThread(^{ + XCTAssertThrows(node.alpha); + }); +} + +- (void)testThatManuallyFlushingTheSyncControllerImmediatelyAppliesChanges +{ + ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + XCTAssertEqual(node.alpha, 1); + ASDispatchSyncOnOtherThread(^{ + node.alpha = 0; + }); + XCTAssertEqual(node.alpha, 1); + [ctrl flush]; + XCTAssertEqual(node.alpha, 0); + XCTAssertFalse(ctrl.test_isFlushScheduled); +} + +- (void)testThatFlushingTheControllerInBackgroundThrows +{ + ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + XCTAssertEqual(node.alpha, 1); + ASDispatchSyncOnOtherThread(^{ + node.alpha = 0; + XCTAssertThrows([ctrl flush]); + }); +} + +- (void)testThatSettingABridgedPropertyOnMainThreadPassesDirectlyToView +{ + ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; + ASDisplayNode *node = [ASDisplayNode new]; + XCTAssertFalse(node.pendingViewState.hasChanges); + [node view]; + XCTAssertEqual(node.alpha, 1); + node.alpha = 0; + XCTAssertEqual(node.view.alpha, 0); + XCTAssertEqual(node.alpha, 0); + XCTAssertFalse(node.pendingViewState.hasChanges); + XCTAssertFalse(ctrl.test_isFlushScheduled); +} + +@end diff --git a/AsyncDisplayKitTests/ASPendingStateControllerTests.m b/AsyncDisplayKitTests/ASPendingStateControllerTests.m deleted file mode 100644 index ef1f583242..0000000000 --- a/AsyncDisplayKitTests/ASPendingStateControllerTests.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// 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 From a46bd8e29b6bbba125ffb33ccad25685b4d92811 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Feb 2016 18:43:40 -0800 Subject: [PATCH 181/224] More tests, all but 1 passing --- AsyncDisplayKit/ASDisplayNode.mm | 24 ++++ .../Private/ASDisplayNode+UIViewBridge.mm | 39 +++--- .../Private/ASDisplayNodeInternal.h | 7 +- .../Private/ASPendingStateController.mm | 4 +- AsyncDisplayKit/Private/_ASPendingState.h | 3 + AsyncDisplayKit/Private/_ASPendingState.m | 10 ++ .../ASBridgedPropertiesTests.mm | 120 +++++++++++++++++- 7 files changed, 179 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0b2f6de2b0..e9ebbab682 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -986,11 +986,20 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( { ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); + + if (_pendingViewState.hasSetNeedsLayout) { + [self __setNeedsLayout]; + } + if (self.layerBacked) { [_pendingViewState applyToLayer:self.layer]; } else { [_pendingViewState applyToView:self.view]; } + + if (_pendingViewState.hasSetNeedsDisplay) { + [self __setNeedsDisplay]; + } [_pendingViewState clearChanges]; } @@ -1049,6 +1058,21 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( } } +// If not rasterized (and therefore we certainly have a view or layer), +// Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded. +// Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay, +// as they don't need to display in the working range at all - since at all times onscreen, one +// -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame. +- (void)__setNeedsDisplay +{ + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); + // FIXME: This should not need to recursively display, so create a non-recursive variant. + // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. + if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + } +} + // These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed. - (void)__layout diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index f33f56ca20..d850f2887c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -76,9 +76,10 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt #define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : self.pendingViewState.layerProperty -#define _setToLayer(layerProperty, layerValueExpr) __loaded ? _layer.layerProperty = (layerValueExpr) : self.pendingViewState.layerProperty = (layerValueExpr) +#define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ +if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingViewState.layerProperty = (layerValueExpr); } -#define _messageToViewOrLayer(viewAndLayerSelector) __loaded ? (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector]) : [self.pendingViewState viewAndLayerSelector] +#define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector]) #define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector] @@ -281,7 +282,6 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setNeedsDisplay { _bridge_prologue_write; - if (_hierarchyState & ASHierarchyStateRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node @@ -299,19 +299,14 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt [rasterizedContainerNode setNeedsDisplay]; }); } else { - // If not rasterized (and therefore we certainly have a view or layer), - // Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded. - // Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay, - // as they don't need to display in the working range at all - since at all times onscreen, one - // -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame. - - _messageToViewOrLayer(setNeedsDisplay); - - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + _messageToViewOrLayer(setNeedsDisplay); + [self __setNeedsDisplay]; + } else { + /// We will call `__setNeedsDisplay` just after the pending state + /// gets applied. + [_pendingViewState setNeedsDisplay]; } } } @@ -319,9 +314,15 @@ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewSt - (void)setNeedsLayout { _bridge_prologue_write; - // FIXME: This method currently asserts on the node's thread affinity. - [self __setNeedsLayout]; - _messageToViewOrLayer(setNeedsLayout); + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + [self __setNeedsLayout]; + _messageToViewOrLayer(setNeedsLayout); + } else { + /// We will call `__setNeedsLayout` just before the pending state + /// gets applied. + [_pendingViewState setNeedsLayout]; + } } - (BOOL)isOpaque diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 498b0ff0cc..bc53ff77c0 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -152,10 +152,15 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (BOOL)__shouldSize; /** - Invoked by a call to setNeedsLayout to the underlying view + Invoked before a call to setNeedsLayout to the underlying view */ - (void)__setNeedsLayout; +/** + Invoked after a call to setNeedsDisplay to the underlying view + */ +- (void)__setNeedsDisplay; + - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 58d20068ba..41a11aed0a 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -76,7 +76,9 @@ } _flags.pendingFlush = YES; - [self performSelectorOnMainThread:@selector(flushNow) withObject:nil waitUntilDone:NO modes:@[ NSRunLoopCommonModes ]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self flushNow]; + }); } /** diff --git a/AsyncDisplayKit/Private/_ASPendingState.h b/AsyncDisplayKit/Private/_ASPendingState.h index 531e81f169..3178747afb 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.h +++ b/AsyncDisplayKit/Private/_ASPendingState.h @@ -30,6 +30,9 @@ + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; + (_ASPendingState *)pendingViewStateFromView:(UIView *)view; +@property (nonatomic, readonly) BOOL hasSetNeedsLayout; +@property (nonatomic, readonly) BOOL hasSetNeedsDisplay; + @property (nonatomic, readonly) BOOL hasChanges; - (void)clearChanges; diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index 85deb9c107..0e522e9f28 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -891,6 +891,16 @@ static UIColor *defaultTintColor = nil; _flags = (ASPendingStateFlags){ 0 }; } +- (BOOL)hasSetNeedsLayout +{ + return _flags.needsLayout; +} + +- (BOOL)hasSetNeedsDisplay +{ + return _flags.needsDisplay; +} + - (BOOL)hasChanges { ASPendingStateFlags flags = _flags; diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm index ae80fd81ff..1e49ac1aa3 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -12,27 +12,42 @@ #import "ASThread.h" #import "ASDisplayNodeInternal.h" #import "_ASPendingState.h" +#import "ASCellNode.h" @interface ASPendingStateController (Testing) - (BOOL)test_isFlushScheduled; @end -@interface ASBridgedPropertiesTests : XCTestCase +@interface ASBridgedPropertiesTestView : UIView +@property (nonatomic, readonly) BOOL receivedSetNeedsLayout; +@end +@implementation ASBridgedPropertiesTestView + +- (void)setNeedsLayout +{ + _receivedSetNeedsLayout = YES; + [super setNeedsLayout]; +} + +@end + +@interface ASBridgedPropertiesTests : XCTestCase @end /// Dispatches the given block synchronously onto a different thread. /// This is useful for testing non-main-thread behavior because `dispatch_sync` /// will often use the current thread. static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { - dispatch_semaphore_t sem = dispatch_semaphore_create(0); + dispatch_group_t group = dispatch_group_create(); dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_group_enter(group); dispatch_async(q, ^{ ASDisplayNodeCAssertNotMainThread(); block(); - dispatch_semaphore_signal(sem); + dispatch_group_leave(group); }); - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } @implementation ASBridgedPropertiesTests @@ -42,7 +57,26 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { XCTAssertNotNil([ASPendingStateController sharedInstance]); } -- (void)testThatSettingABridgedPropertyInBackgroundGetsFlushedOnNextRunLoop +- (void)testThatDirtyNodesAreNotRetained +{ + ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; + __weak ASDisplayNode *weakNode = nil; + @autoreleasepool { + ASDisplayNode *node = [ASDisplayNode new]; + weakNode = node; + [node view]; + XCTAssertEqual(node.alpha, 1); + ASDispatchSyncOnOtherThread(^{ + node.alpha = 0; + }); + XCTAssertEqual(node.alpha, 1); + XCTAssert(ctrl.test_isFlushScheduled); + XCTAssertNotNil(weakNode); + } + XCTAssertNil(weakNode); +} + +- (void)testThatSettingABridgedViewPropertyInBackgroundGetsFlushedOnNextRunLoop { ASDisplayNode *node = [ASDisplayNode new]; [node view]; @@ -50,11 +84,25 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { ASDispatchSyncOnOtherThread(^{ node.alpha = 0; }); - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + XCTAssertEqual(node.alpha, 1); + [self waitForMainDispatchQueueToFlush]; XCTAssertEqual(node.alpha, 0); } -- (void)testThatReadingABridgedPropertyInBackgroundThrowsAnException +- (void)testThatSettingABridgedLayerPropertyInBackgroundGetsFlushedOnNextRunLoop +{ + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + XCTAssertEqual(node.shadowOpacity, 0); + ASDispatchSyncOnOtherThread(^{ + node.shadowOpacity = 1; + }); + XCTAssertEqual(node.shadowOpacity, 0); + [self waitForMainDispatchQueueToFlush]; + XCTAssertEqual(node.shadowOpacity, 1); +} + +- (void)testThatReadingABridgedViewPropertyInBackgroundThrowsAnException { ASDisplayNode *node = [ASDisplayNode new]; [node view]; @@ -63,6 +111,15 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { }); } +- (void)testThatReadingABridgedLayerPropertyInBackgroundThrowsAnException +{ + ASDisplayNode *node = [ASDisplayNode new]; + [node view]; + ASDispatchSyncOnOtherThread(^{ + XCTAssertThrows(node.contentsScale); + }); +} + - (void)testThatManuallyFlushingTheSyncControllerImmediatelyAppliesChanges { ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; @@ -104,4 +161,53 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { XCTAssertFalse(ctrl.test_isFlushScheduled); } +- (void)testThatCallingSetNeedsLayoutFromBackgroundCausesItToHappenLater +{ + ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewClass:ASBridgedPropertiesTestView.class]; + ASBridgedPropertiesTestView *view = (ASBridgedPropertiesTestView *)node.view; + XCTAssertFalse(view.receivedSetNeedsLayout); + ASDispatchSyncOnOtherThread(^{ + XCTAssertNoThrow([node setNeedsLayout]); + }); + XCTAssertFalse(view.receivedSetNeedsLayout); + [self waitForMainDispatchQueueToFlush]; + XCTAssertTrue(view.receivedSetNeedsLayout); +} + +- (void)testThatCallingSetNeedsLayoutOnACellNodeFromBackgroundIsSafe +{ + ASCellNode *node = [ASCellNode new]; + [node view]; + ASDispatchSyncOnOtherThread(^{ + XCTAssertNoThrow([node setNeedsLayout]); + }); +} + +- (void)testThatCallingSetNeedsDisplayFromBackgroundCausesItToHappenLater +{ + ASDisplayNode *node = [ASDisplayNode new]; + [node.layer displayIfNeeded]; + XCTAssertFalse(node.layer.needsDisplay); + ASDispatchSyncOnOtherThread(^{ + XCTAssertNoThrow([node setNeedsDisplay]); + }); + XCTAssertFalse(node.layer.needsDisplay); + [self waitForMainDispatchQueueToFlush]; + XCTAssertTrue(node.layer.needsDisplay); +} + +/// [XCTExpectation expectationWithPredicate:] should handle this +/// but under Xcode 7.2.1 its polling interval is 1 second +/// which makes the tests really slow and I'm impatient. +- (void)waitForMainDispatchQueueToFlush +{ + __block BOOL done = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + done = YES; + }); + while (!done) { + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } +} + @end From 7bbe401727999c1bb61eb9dfb32d320a84048bfb Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Feb 2016 19:28:40 -0800 Subject: [PATCH 182/224] Workaround deadlock when running table view tests in isolation --- AsyncDisplayKitTests/ASTableViewTests.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 2949a16e2f..550f3fec9d 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -144,6 +144,18 @@ @implementation ASTableViewTests +- (void)setUp +{ + /// Load a display node before the first test. + /// Without this, running this suite specifically + /// (as opposed to all tests) will cause a deadlock + /// because of the dispatch_sync in `ASScreenScale()`. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [ASDisplayNode new]; + }); +} + // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { From e87e8e2203a5af2013978a0be59c40a7784bec5f Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Feb 2016 19:43:12 -0800 Subject: [PATCH 183/224] Discard gunk --- AsyncDisplayKitTests/ASDisplayNodeTests.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index e86c10484e..455591cd1b 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -425,12 +425,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\ - (void)checkSimpleBridgePropertiesSetPropagate:(BOOL)isLayerBacked { - /// The first node we instantiate must be created on the main thread - /// in order to read ASScreenScale() safely. We create this throwaway - /// node so that running this test first in the suite - /// doesn't cause a deadlock. - [ASDisplayNode new]; - __block ASDisplayNode *node = nil; [self executeOffThread:^{ @@ -490,7 +484,13 @@ for (ASDisplayNode *n in @[ nodes ]) {\ [self checkValuesMatchSetValues:node isLayerBacked:isLayerBacked]; - // TODO: Handle backwards propagation i.e. from view/layer to node. + // As a final sanity check, change a value on the realized view and ensure it is fetched through the node. + if (isLayerBacked) { + node.layer.hidden = NO; + } else { + node.view.hidden = NO; + } + XCTAssertEqual(NO, node.hidden, @"After the view is realized, the node should delegate properties to the view."); } // Set each of the simple bridged UIView properties to a non-default value off-thread, then From 71edc810d3f708ca2e0dc95187f74bb55054fd9c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Feb 2016 19:45:01 -0800 Subject: [PATCH 184/224] Revert unnecessary change to [ASDisplayNode setOpaque:] --- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index d850f2887c..12d9de166d 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -333,10 +333,14 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie - (void)setOpaque:(BOOL)newOpaque { - _bridge_prologue_write; - _setToViewOrLayer(opaque, newOpaque, opaque, newOpaque); + BOOL prevOpaque = self.opaque; - // TODO: Mark as needs display if value changed? + _bridge_prologue_write; + _setToLayer(opaque, newOpaque); + + if (prevOpaque != newOpaque) { + [self setNeedsDisplay]; + } } - (BOOL)isUserInteractionEnabled From a3a38b4b5e791146766aacdf8f4d14462b65a3d2 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Feb 2016 13:39:45 -0800 Subject: [PATCH 185/224] Add thread affinity demo --- .../project.pbxproj | 357 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata | 10 + .../AppDelegate.swift | 50 +++ .../AppIcon.appiconset/Contents.json | 38 ++ .../Base.lproj/LaunchScreen.storyboard | 27 ++ .../BackgroundPropertySetting/Info.plist | 36 ++ .../ViewController.swift | 64 ++++ examples/BackgroundPropertySetting/Podfile | 4 + 9 files changed, 593 insertions(+) create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift create mode 100644 examples/BackgroundPropertySetting/Podfile diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..1e3b07f3c0 --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj @@ -0,0 +1,357 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */; }; + CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736E1C751C8A00AB7199 /* ViewController.swift */; }; + CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCD373731C751C8A00AB7199 /* Assets.xcassets */; }; + CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */; }; + FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D966CA4D089E4178A58E447C /* Pods.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BackgroundPropertySetting.app; sourceTree = BUILT_PRODUCTS_DIR; }; + CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + CCD3736E1C751C8A00AB7199 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + CCD373731C751C8A00AB7199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + CCD373761C751C8A00AB7199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + CCD373781C751C8A00AB7199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D966CA4D089E4178A58E447C /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CCD373661C751C8A00AB7199 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 30042A321E6A9E291E310396 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D966CA4D089E4178A58E447C /* Pods.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9C878D1471151DC350759386 /* Pods */ = { + isa = PBXGroup; + children = ( + 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */, + 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + CCD373601C751C8A00AB7199 = { + isa = PBXGroup; + children = ( + CCD3736B1C751C8A00AB7199 /* BackgroundPropertySetting */, + CCD3736A1C751C8A00AB7199 /* Products */, + 9C878D1471151DC350759386 /* Pods */, + 30042A321E6A9E291E310396 /* Frameworks */, + ); + sourceTree = ""; + }; + CCD3736A1C751C8A00AB7199 /* Products */ = { + isa = PBXGroup; + children = ( + CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */, + ); + name = Products; + sourceTree = ""; + }; + CCD3736B1C751C8A00AB7199 /* BackgroundPropertySetting */ = { + isa = PBXGroup; + children = ( + CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */, + CCD3736E1C751C8A00AB7199 /* ViewController.swift */, + CCD373731C751C8A00AB7199 /* Assets.xcassets */, + CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */, + CCD373781C751C8A00AB7199 /* Info.plist */, + ); + path = BackgroundPropertySetting; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CCD373681C751C8A00AB7199 /* BackgroundPropertySetting */ = { + isa = PBXNativeTarget; + buildConfigurationList = CCD3737B1C751C8A00AB7199 /* Build configuration list for PBXNativeTarget "BackgroundPropertySetting" */; + buildPhases = ( + 8DB9E5B4CBB8681DB7A8C0F6 /* Check Pods Manifest.lock */, + CCD373651C751C8A00AB7199 /* Sources */, + CCD373661C751C8A00AB7199 /* Frameworks */, + CCD373671C751C8A00AB7199 /* Resources */, + EDCBBE7D585831EB6D1AAC3F /* Embed Pods Frameworks */, + 34D9C63D3C5940DDB4DFF82C /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BackgroundPropertySetting; + productName = BackgroundPropertySetting; + productReference = CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CCD373611C751C8A00AB7199 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Adlai Holler"; + TargetAttributes = { + CCD373681C751C8A00AB7199 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = CCD373641C751C8A00AB7199 /* Build configuration list for PBXProject "BackgroundPropertySetting" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CCD373601C751C8A00AB7199; + productRefGroup = CCD3736A1C751C8A00AB7199 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CCD373681C751C8A00AB7199 /* BackgroundPropertySetting */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CCD373671C751C8A00AB7199 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */, + CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 34D9C63D3C5940DDB4DFF82C /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8DB9E5B4CBB8681DB7A8C0F6 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + EDCBBE7D585831EB6D1AAC3F /* 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/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CCD373651C751C8A00AB7199 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */, + CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CCD373761C751C8A00AB7199 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + CCD373791C751C8A00AB7199 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CCD3737A1C751C8A00AB7199 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CCD3737C1C751C8A00AB7199 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = BackgroundPropertySetting/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = adlai.BackgroundPropertySetting; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + CCD3737D1C751C8A00AB7199 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = BackgroundPropertySetting/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = adlai.BackgroundPropertySetting; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CCD373641C751C8A00AB7199 /* Build configuration list for PBXProject "BackgroundPropertySetting" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCD373791C751C8A00AB7199 /* Debug */, + CCD3737A1C751C8A00AB7199 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CCD3737B1C751C8A00AB7199 /* Build configuration list for PBXNativeTarget "BackgroundPropertySetting" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCD3737C1C751C8A00AB7199 /* Debug */, + CCD3737D1C751C8A00AB7199 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CCD373611C751C8A00AB7199 /* Project object */; +} diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..095df1d899 --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7f92fa35eb --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift b/examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift new file mode 100644 index 0000000000..ad9b18050a --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift @@ -0,0 +1,50 @@ +// +// AppDelegate.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + let window = UIWindow(frame: UIScreen.mainScreen().bounds) + self.window = window + let vc = ViewController() + window.rootViewController = UINavigationController(rootViewController: vc) + window.makeKeyAndVisible() + return true + } + + func applicationWillResignActive(application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..2e721e1833 --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist new file mode 100644 index 0000000000..61861abb1a --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift b/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift new file mode 100644 index 0000000000..cd92abb4cf --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift @@ -0,0 +1,64 @@ +// +// ViewController.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit +import AsyncDisplayKit + +final class ViewController: ASViewController, ASTableDelegate, ASTableDataSource { + + var tableNode: ASTableNode { + return node as! ASTableNode + } + + init() { + super.init(node: ASTableNode(style: .Plain)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Update", style: .Plain, target: self, action: "didTapUpdateButton") + tableNode.delegate = self + tableNode.dataSource = self + title = "Background Node Updating Demo" + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + let rowCount = 20 + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rowCount + } + + func tableView(tableView: ASTableView, nodeBlockForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { + return { + let node = ASCellNode() + node.backgroundColor = getRandomColor() + return node + } + } + + @objc private func didTapUpdateButton() { + let currentlyVisibleNodes = tableNode.view.visibleNodes() + let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + dispatch_async(queue) { + for case let node as ASCellNode in currentlyVisibleNodes { + node.backgroundColor = getRandomColor() + } + } + } +} + +func getRandomColor() -> UIColor{ + + let randomRed:CGFloat = CGFloat(drand48()) + + let randomGreen:CGFloat = CGFloat(drand48()) + + let randomBlue:CGFloat = CGFloat(drand48()) + + return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0) + +} diff --git a/examples/BackgroundPropertySetting/Podfile b/examples/BackgroundPropertySetting/Podfile new file mode 100644 index 0000000000..3ca3b0ba09 --- /dev/null +++ b/examples/BackgroundPropertySetting/Podfile @@ -0,0 +1,4 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +use_frameworks! +pod 'AsyncDisplayKit', :path => '../..' From b0cbd2dd599b72018be56df3ade1313a7d4f07c7 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Feb 2016 13:40:00 -0800 Subject: [PATCH 186/224] Fix background color issue --- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 12d9de166d..e1fbe38a11 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -540,15 +540,10 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie - (void)setBackgroundColor:(UIColor *)newBackgroundColor { - UIColor *prevBackgroundColor = self.backgroundColor; - _bridge_prologue_write; _setToLayer(backgroundColor, newBackgroundColor.CGColor); - - // Note: This check assumes that the colors are within the same color space. - if (!ASObjectIsEqual(prevBackgroundColor, newBackgroundColor)) { - [self setNeedsDisplay]; - } + // FIXME: Would like to setNeedsDisplay if background color changed, but + // not safe to read old color in background. } - (UIColor *)tintColor From 7a6006e627fcfd9556ca32df28d8f274fd55357e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Feb 2016 13:41:13 -0800 Subject: [PATCH 187/224] Apply same fix on opaqueness --- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index e1fbe38a11..6cbbb6c518 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -333,14 +333,11 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie - (void)setOpaque:(BOOL)newOpaque { - BOOL prevOpaque = self.opaque; - _bridge_prologue_write; _setToLayer(opaque, newOpaque); - if (prevOpaque != newOpaque) { - [self setNeedsDisplay]; - } + // FIXME: Would like to setNeedsDisplay if opaqueness changed, but + // not safe to read old value in background. } - (BOOL)isUserInteractionEnabled From 563d0893a33f9e005797d7f4ea4a2724016c2bdf Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Feb 2016 18:30:42 -0800 Subject: [PATCH 188/224] Beef up the properties, beef up the demo app --- AsyncDisplayKit/ASCellNode.m | 3 +- AsyncDisplayKit/ASDisplayNode.mm | 13 ++- .../Private/ASDisplayNode+UIViewBridge.mm | 11 ++- AsyncDisplayKit/Private/_ASPendingState.m | 19 ++-- .../project.pbxproj | 8 ++ .../DemoCellNode.swift | 87 ++++++++++++++++++ .../BackgroundPropertySetting/Utilities.swift | 15 +++ .../ViewController.swift | 91 +++++++++++++------ 8 files changed, 197 insertions(+), 50 deletions(-) create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift create mode 100644 examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index b594cfcce2..bc980bba13 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -94,13 +94,12 @@ - (void)setNeedsLayout { - ASDisplayNodeAssertThreadAffinity(self); CGSize oldSize = self.calculatedSize; [super setNeedsLayout]; if (_layoutDelegate != nil && self.isNodeLoaded) { - BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize); ASPerformBlockOnMainThread(^{ + BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize); [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; }); } diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e9ebbab682..d874378617 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -628,7 +628,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); if (![self __shouldSize]) return nil; @@ -1868,7 +1867,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); if (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) { ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; layoutSpec.isMutable = NO; @@ -1895,25 +1894,25 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); return _preferredFrameSize; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); return nil; } - (ASLayout *)calculatedLayout { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); return _layout; } - (CGSize)calculatedSize { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); return _layout.size; } @@ -1944,7 +1943,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)invalidateCalculatedLayout { - ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); // This will cause -measureWithSizeRange: to actually compute the size instead of returning the previously cached size _flags.isMeasured = NO; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 6cbbb6c518..86c04ba4e2 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -335,9 +335,9 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie { _bridge_prologue_write; _setToLayer(opaque, newOpaque); - - // FIXME: Would like to setNeedsDisplay if opaqueness changed, but - // not safe to read old value in background. + // NOTE: If we're in the background, then when the pending state + // is applied to the view on main, we will call `setNeedsDisplay` if + // the new opaque value doesn't match the one on the layer. } - (BOOL)isUserInteractionEnabled @@ -539,8 +539,9 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie { _bridge_prologue_write; _setToLayer(backgroundColor, newBackgroundColor.CGColor); - // FIXME: Would like to setNeedsDisplay if background color changed, but - // not safe to read old color in background. + // NOTE: If we're in the background, then when the pending state + // is applied to the view on main, we will call `setNeedsDisplay` if + // the new background color doesn't match the one on the layer. } - (UIColor *)tintColor diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index 0e522e9f28..09bb916597 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -557,6 +557,13 @@ static UIColor *defaultTintColor = nil; - (void)applyToLayer:(CALayer *)layer { ASPendingStateFlags flags = _flags; + + if (flags.needsDisplay + || (flags.setOpaque && opaque != layer.opaque) + || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) { + [layer setNeedsDisplay]; + } + if (flags.setAnchorPoint) layer.anchorPoint = anchorPoint; @@ -629,9 +636,6 @@ static UIColor *defaultTintColor = nil; if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsDisplay) - [layer setNeedsDisplay]; - if (flags.needsLayout) [layer setNeedsLayout]; @@ -658,6 +662,12 @@ static UIColor *defaultTintColor = nil; CALayer *layer = view.layer; ASPendingStateFlags flags = _flags; + if (flags.needsDisplay + || (flags.setOpaque && opaque != view.opaque) + || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) { + [view setNeedsDisplay]; + } + if (flags.setAnchorPoint) layer.anchorPoint = anchorPoint; @@ -752,9 +762,6 @@ static UIColor *defaultTintColor = nil; if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsDisplay) - [view setNeedsDisplay]; - if (flags.needsLayout) [view setNeedsLayout]; diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj index 1e3b07f3c0..a90b8ebb7b 100644 --- a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736E1C751C8A00AB7199 /* ViewController.swift */; }; CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCD373731C751C8A00AB7199 /* Assets.xcassets */; }; CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */; }; + CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */; }; + CCD373811C75228900AB7199 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD373801C75228900AB7199 /* Utilities.swift */; }; FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D966CA4D089E4178A58E447C /* Pods.framework */; }; /* End PBXBuildFile section */ @@ -23,6 +25,8 @@ CCD373731C751C8A00AB7199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CCD373761C751C8A00AB7199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CCD373781C751C8A00AB7199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoCellNode.swift; sourceTree = ""; }; + CCD373801C75228900AB7199 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; D966CA4D089E4178A58E447C /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -78,6 +82,8 @@ children = ( CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */, CCD3736E1C751C8A00AB7199 /* ViewController.swift */, + CCD373801C75228900AB7199 /* Utilities.swift */, + CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */, CCD373731C751C8A00AB7199 /* Assets.xcassets */, CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */, CCD373781C751C8A00AB7199 /* Info.plist */, @@ -206,7 +212,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */, CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */, + CCD373811C75228900AB7199 /* Utilities.swift in Sources */, CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift b/examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift new file mode 100644 index 0000000000..63524a540d --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift @@ -0,0 +1,87 @@ +// +// DemoCellNode.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit +import AsyncDisplayKit + +final class DemoCellNode: ASCellNode { + let childA = ASDisplayNode() + let childB = ASDisplayNode() + var state = State.Right + + override init() { + super.init() + usesImplicitHierarchyManagement = true + } + + override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec { + let specA = ASRatioLayoutSpec(ratio: 1, child: childA) + specA.flexBasis = ASRelativeDimensionMakeWithPoints(1) + specA.flexGrow = true + let specB = ASRatioLayoutSpec(ratio: 1, child: childB) + specB.flexBasis = ASRelativeDimensionMakeWithPoints(1) + specB.flexGrow = true + let children = state.isReverse ? [ specB, specA ] : [ specA, specB ] + let direction: ASStackLayoutDirection = state.isVertical ? .Vertical : .Horizontal + return ASStackLayoutSpec(direction: direction, + spacing: 20, + justifyContent: .SpaceAround, + alignItems: .Center, + children: children) + } + + override func animateLayoutTransition(context: ASContextTransitioning!) { + childA.frame = context.initialFrameForNode(childA) + childB.frame = context.initialFrameForNode(childB) + let tinyDelay = drand48() / 10 + UIView.animateWithDuration(0.5, delay: tinyDelay, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5, options: .BeginFromCurrentState, animations: { () -> Void in + self.childA.frame = context.finalFrameForNode(self.childA) + self.childB.frame = context.finalFrameForNode(self.childB) + }, completion: { + context.completeTransition($0) + }) + } + + enum State { + case Right + case Up + case Left + case Down + + var isVertical: Bool { + switch self { + case .Up, .Down: + return true + default: + return false + } + } + + var isReverse: Bool { + switch self { + case .Left, .Up: + return true + default: + return false + } + } + + mutating func advance() { + switch self { + case .Right: + self = .Up + case .Up: + self = .Left + case .Left: + self = .Down + case .Down: + self = .Right + } + } + } +} diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift new file mode 100644 index 0000000000..5aedf39722 --- /dev/null +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift @@ -0,0 +1,15 @@ +// +// Utilities.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit + +extension UIColor { + static func random() -> UIColor { + return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0) + } +} diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift b/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift index cd92abb4cf..1573b9ebeb 100644 --- a/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift +++ b/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift @@ -9,56 +9,87 @@ import UIKit import AsyncDisplayKit -final class ViewController: ASViewController, ASTableDelegate, ASTableDataSource { +final class ViewController: ASViewController, ASCollectionDelegate, ASCollectionDataSource { + let itemCount = 1000 - var tableNode: ASTableNode { - return node as! ASTableNode + let itemSize: CGSize + let padding: CGFloat + var collectionNode: ASCollectionNode { + return node as! ASCollectionNode } init() { - super.init(node: ASTableNode(style: .Plain)) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Update", style: .Plain, target: self, action: "didTapUpdateButton") - tableNode.delegate = self - tableNode.dataSource = self - title = "Background Node Updating Demo" + let layout = UICollectionViewFlowLayout() + (padding, itemSize) = ViewController.computeLayoutSizesForMainScreen() + layout.minimumInteritemSpacing = padding + layout.minimumLineSpacing = padding + super.init(node: ASCollectionNode(collectionViewLayout: layout)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Color", style: .Plain, target: self, action: "didTapColorsButton") + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Layout", style: .Plain, target: self, action: "didTapLayoutButton") + collectionNode.delegate = self + collectionNode.dataSource = self + title = "Background Updating" } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - let rowCount = 20 - func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return rowCount + // MARK: ASCollectionDataSource + + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return itemCount } - func tableView(tableView: ASTableView, nodeBlockForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { + func collectionView(collectionView: ASCollectionView, nodeBlockForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { return { - let node = ASCellNode() - node.backgroundColor = getRandomColor() + let node = DemoCellNode() + node.backgroundColor = UIColor.random() + node.childA.backgroundColor = UIColor.random() + node.childB.backgroundColor = UIColor.random() return node } } - @objc private func didTapUpdateButton() { - let currentlyVisibleNodes = tableNode.view.visibleNodes() + func collectionView(collectionView: ASCollectionView, constrainedSizeForNodeAtIndexPath indexPath: NSIndexPath) -> ASSizeRange { + return ASSizeRangeMake(itemSize, itemSize) + } + + // MARK: Action Handling + + @objc private func didTapColorsButton() { + let currentlyVisibleNodes = collectionNode.view.visibleNodes() let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) dispatch_async(queue) { - for case let node as ASCellNode in currentlyVisibleNodes { - node.backgroundColor = getRandomColor() + for case let node as DemoCellNode in currentlyVisibleNodes { + node.backgroundColor = UIColor.random() } } } -} - -func getRandomColor() -> UIColor{ - - let randomRed:CGFloat = CGFloat(drand48()) - - let randomGreen:CGFloat = CGFloat(drand48()) - - let randomBlue:CGFloat = CGFloat(drand48()) - - return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0) - + + @objc private func didTapLayoutButton() { + let currentlyVisibleNodes = collectionNode.view.visibleNodes() + let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + dispatch_async(queue) { + for case let node as DemoCellNode in currentlyVisibleNodes { + node.state.advance() + node.setNeedsLayout() + } + } + } + + // MARK: Static + + static func computeLayoutSizesForMainScreen() -> (padding: CGFloat, itemSize: CGSize) { + let numberOfColumns = 4 + let screen = UIScreen.mainScreen() + let scale = screen.scale + let screenWidth = Int(screen.bounds.width * screen.scale) + let itemWidthPx = (screenWidth - (numberOfColumns - 1)) / numberOfColumns + let leftover = screenWidth - itemWidthPx * numberOfColumns + let paddingPx = leftover / (numberOfColumns - 1) + let itemDimension = CGFloat(itemWidthPx) / scale + let padding = CGFloat(paddingPx) / scale + return (padding: padding, itemSize: CGSize(width: itemDimension, height: itemDimension)) + } } From 636c8c04f3127b745fe3e0238d31267318c73cb9 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Feb 2016 19:49:45 -0800 Subject: [PATCH 189/224] Improve bridging of frame property --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++--- AsyncDisplayKit/ASDisplayNode.mm | 3 +- .../Private/ASDisplayNode+UIViewBridge.mm | 51 ++++++++----------- AsyncDisplayKit/Private/ASInternalHelpers.h | 7 +++ AsyncDisplayKit/Private/_ASPendingState.h | 2 +- .../{_ASPendingState.m => _ASPendingState.mm} | 50 ++++++++++++++---- 6 files changed, 76 insertions(+), 49 deletions(-) rename AsyncDisplayKit/Private/{_ASPendingState.m => _ASPendingState.mm} (93%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 09d20de456..90fc1b54f4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -63,7 +63,7 @@ 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; - 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.m */; }; + 058D0A27195D050800B7D73C /* _ASPendingState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.mm */; }; 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */; }; 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */; }; @@ -433,7 +433,7 @@ B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; }; - B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.m */; }; + B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.mm */; }; B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; }; B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */; }; @@ -615,7 +615,7 @@ 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCoreAnimationExtras.h; sourceTree = ""; }; 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCoreAnimationExtras.mm; sourceTree = ""; }; 058D0A05195D050800B7D73C /* _ASPendingState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASPendingState.h; sourceTree = ""; }; - 058D0A06195D050800B7D73C /* _ASPendingState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASPendingState.m; sourceTree = ""; }; + 058D0A06195D050800B7D73C /* _ASPendingState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASPendingState.mm; sourceTree = ""; }; 058D0A07195D050800B7D73C /* _ASScopeTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASScopeTimer.h; sourceTree = ""; }; 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+AsyncDisplay.mm"; sourceTree = ""; }; 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+DebugTiming.h"; sourceTree = ""; }; @@ -1155,7 +1155,7 @@ 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, 058D0A05195D050800B7D73C /* _ASPendingState.h */, - 058D0A06195D050800B7D73C /* _ASPendingState.m */, + 058D0A06195D050800B7D73C /* _ASPendingState.mm */, 058D0A07195D050800B7D73C /* _ASScopeTimer.h */, 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, 044285051BAA63FE00D16268 /* ASBatchFetching.h */, @@ -1794,7 +1794,7 @@ 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */, 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */, - 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */, + 058D0A27195D050800B7D73C /* _ASPendingState.mm in Sources */, 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */, ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */, 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, @@ -1938,7 +1938,7 @@ B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */, B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, - B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */, + B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index d874378617..9e5f0c8cea 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -993,7 +993,8 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( if (self.layerBacked) { [_pendingViewState applyToLayer:self.layer]; } else { - [_pendingViewState applyToView:self.view]; + BOOL setFrameDirectly = (_flags.synchronous && !_flags.layerBacked); + [_pendingViewState applyToView:self.view setFrameDirectly:setFrameDirectly]; } if (_pendingViewState.hasSetNeedsDisplay) { diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 86c04ba4e2..006eba23ec 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -235,8 +235,10 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie - (void)setFrame:(CGRect)rect { _bridge_prologue_write; - - if (_flags.synchronous && !_flags.layerBacked) { + BOOL setFrameDirectly = _flags.synchronous && !_flags.layerBacked; + BOOL isMainThread = ASDisplayNodeThreadIsMain(); + BOOL nodeLoaded = self.nodeLoaded; + if (nodeLoaded && isMainThread && setFrameDirectly) { // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform @@ -244,38 +246,25 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif - _setToViewOnly(frame, rect); - } else { - // This is by far the common case / hot path. - [self __setSafeFrame:rect]; - } -} + _view.frame = rect; + } else if (!nodeLoaded || (isMainThread && !setFrameDirectly)) { + /** + * Sets a new frame to this node by changing its bounds and position. This method can be safely called even if + * the transform is a non-identity transform, because bounds and position can be set instead of frame. + * This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call. + * A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set. + */ + CGRect bounds; + CGPoint position; + ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position); -/** - * Sets a new frame to this node by changing its bounds and position. This method can be safely called even if - * the transform is a non-identity transform, because bounds and position can be set instead of frame. - * This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call. - * A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set. - */ -- (void)__setSafeFrame:(CGRect)rect -{ - _bridge_prologue_write; - - BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); - - CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin); - CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); - - CGRect bounds = (CGRect){ origin, rect.size }; - CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); - - if (useLayer) { - _layer.bounds = bounds; - _layer.position = position; - } else { self.bounds = bounds; self.position = position; + } else if (nodeLoaded && !isMainThread) { + if (!_pendingViewState.hasChanges) { + [ASPendingStateController.sharedInstance registerNode:self]; + } + _pendingViewState.frame = rect; } } diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 11709e8373..de661d24b5 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -49,6 +49,13 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, } } +ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) +{ + *bounds = (CGRect){ origin, rect.size }; + *position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, + rect.origin.y + rect.size.height * anchorPoint.y); +} + @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/AsyncDisplayKit/Private/_ASPendingState.h b/AsyncDisplayKit/Private/_ASPendingState.h index 3178747afb..8f6702465f 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.h +++ b/AsyncDisplayKit/Private/_ASPendingState.h @@ -24,7 +24,7 @@ // Supports all of the properties included in the ASDisplayNodeViewProperties protocol -- (void)applyToView:(UIView *)view; +- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly; - (void)applyToLayer:(CALayer *)layer; + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.mm similarity index 93% rename from AsyncDisplayKit/Private/_ASPendingState.m rename to AsyncDisplayKit/Private/_ASPendingState.mm index 09bb916597..007eeedad4 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -11,6 +11,8 @@ #import "_ASCoreAnimationExtras.h" #import "_ASAsyncTransactionContainer.h" #import "ASAssert.h" +#import "ASInternalHelpers.h" +#import "ASDisplayNodeInternal.h" typedef struct { // Properties @@ -567,15 +569,9 @@ static UIColor *defaultTintColor = nil; if (flags.setAnchorPoint) layer.anchorPoint = anchorPoint; - if (flags.setPosition) - layer.position = position; - if (flags.setZPosition) layer.zPosition = zPosition; - if (flags.setBounds) - layer.bounds = bounds; - if (flags.setContentsScale) layer.contentsScale = contentsScale; @@ -644,12 +640,22 @@ static UIColor *defaultTintColor = nil; if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - - if (flags.setFrame) - ASDisplayNodeAssert(NO, @"Frame property should only be used for synchronously wrapped nodes. See setFrame: in ASDisplayNode+UIViewBridge"); + + if (flags.setFrame) { + CGRect _bounds; + CGPoint _position; + ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); + layer.bounds = _bounds; + layer.position = _position; + } else { + if (flags.setBounds) + layer.bounds = bounds; + if (flags.setPosition) + layer.position = position; + } } -- (void)applyToView:(UIView *)view +- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly { /* Use our convenience setters blah here instead of layer.blah @@ -803,6 +809,30 @@ static UIColor *defaultTintColor = nil; if (flags.setAccessibilityIdentifier) view.accessibilityIdentifier = accessibilityIdentifier; + + if (flags.setFrame) { + if (setFrameDirectly) { + // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: + + // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform +#if DEBUG + // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. + ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); +#endif + view.frame = frame; + } else { + CGRect _bounds; + CGPoint _position; + ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); + layer.bounds = _bounds; + layer.position = _position; + } + } else { + if (flags.setBounds) + layer.bounds = bounds; + if (flags.setPosition) + layer.position = position; + } } // FIXME: Make this more efficient by tracking which properties are set rather than reading everything. From e9712cdfa1c23cf1e4ee4c1853f3f368ec672d5b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 14:29:45 -0800 Subject: [PATCH 190/224] Make the tests sturdier, improve setNeedsLayout/setNeedsDisplay bridging --- AsyncDisplayKit/ASDisplayNode.mm | 6 +-- .../Private/ASDisplayNode+UIViewBridge.mm | 38 ++++++++++++------- .../ASBridgedPropertiesTests.mm | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 9e5f0c8cea..d68ca31a70 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -986,6 +986,9 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_propertyLock); + // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout + // but implicit hierarchy management would require us to modify the node tree + // in the background on a loaded node, which isn't currently supported. if (_pendingViewState.hasSetNeedsLayout) { [self __setNeedsLayout]; } @@ -997,9 +1000,6 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( [_pendingViewState applyToView:self.view setFrameDirectly:setFrameDirectly]; } - if (_pendingViewState.hasSetNeedsDisplay) { - [self __setNeedsDisplay]; - } [_pendingViewState clearChanges]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 006eba23ec..8423ed0fac 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -288,14 +288,18 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie [rasterizedContainerNode setNeedsDisplay]; }); } else { - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - if (shouldApply) { - _messageToViewOrLayer(setNeedsDisplay); - [self __setNeedsDisplay]; + if (self.nodeLoaded) { + if (ASDisplayNodeThreadIsMain()) { + _messageToViewOrLayer(setNeedsDisplay); + } else { + if (!_pendingViewState.hasChanges) { + [ASPendingStateController.sharedInstance registerNode:self]; + } + [self __setNeedsDisplay]; + [_pendingViewState setNeedsDisplay]; + } } else { - /// We will call `__setNeedsDisplay` just after the pending state - /// gets applied. - [_pendingViewState setNeedsDisplay]; + [self __setNeedsDisplay]; } } } @@ -303,14 +307,20 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie - (void)setNeedsLayout { _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - if (shouldApply) { - [self __setNeedsLayout]; - _messageToViewOrLayer(setNeedsLayout); + if (self.nodeLoaded) { + if (ASDisplayNodeThreadIsMain()) { + _messageToViewOrLayer(setNeedsLayout); + } else { + if (!_pendingViewState.hasChanges) { + [ASPendingStateController.sharedInstance registerNode:self]; + } + // NOTE: We will call [self __setNeedsLayout] just before we apply + // the pending state. We need to call it on main if the node is loaded + // to support implicit hierarchy management. + [_pendingViewState setNeedsLayout]; + } } else { - /// We will call `__setNeedsLayout` just before the pending state - /// gets applied. - [_pendingViewState setNeedsLayout]; + [self __setNeedsLayout]; } } diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm index 1e49ac1aa3..5ec77c5dcb 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -62,7 +62,7 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; __weak ASDisplayNode *weakNode = nil; @autoreleasepool { - ASDisplayNode *node = [ASDisplayNode new]; + __attribute__((objc_precise_lifetime)) ASDisplayNode *node = [ASDisplayNode new]; weakNode = node; [node view]; XCTAssertEqual(node.alpha, 1); From 78c8b039ec4e8b0e4334a77acce86bd85af03c2b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 15:23:13 -0800 Subject: [PATCH 191/224] [Example-BackgroundPropertySetting] Rename example project to be compatible with build script --- .../project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../contents.xcworkspacedata | 0 .../AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Base.lproj/LaunchScreen.storyboard | 27 ++ .../DemoCellNode.swift | 0 .../BackgroundPropertySetting/Info.plist | 0 .../BackgroundPropertySetting/Utilities.swift | 0 .../ViewController.swift | 0 .../Sample.xcodeproj/project.pbxproj | 368 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata | 10 + .../Sample/AppDelegate.swift | 50 +++ .../AppIcon.appiconset/Contents.json | 38 ++ .../Base.lproj/LaunchScreen.storyboard | 0 .../Sample/DemoCellNode.swift | 87 +++++ .../Sample/Info.plist | 36 ++ .../Sample/Utilities.swift | 15 + .../Sample/ViewController.swift | 95 +++++ 20 files changed, 733 insertions(+) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting.xcodeproj/project.pbxproj (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/AppDelegate.swift (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) create mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/DemoCellNode.swift (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/Info.plist (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/Utilities.swift (100%) rename examples/BackgroundPropertySetting/{ => Old}/BackgroundPropertySetting/ViewController.swift (100%) create mode 100644 examples/BackgroundPropertySetting/Sample.xcodeproj/project.pbxproj create mode 100644 examples/BackgroundPropertySetting/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/BackgroundPropertySetting/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/BackgroundPropertySetting/Sample/AppDelegate.swift create mode 100644 examples/BackgroundPropertySetting/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json rename examples/BackgroundPropertySetting/{BackgroundPropertySetting => Sample}/Base.lproj/LaunchScreen.storyboard (100%) create mode 100644 examples/BackgroundPropertySetting/Sample/DemoCellNode.swift create mode 100644 examples/BackgroundPropertySetting/Sample/Info.plist create mode 100644 examples/BackgroundPropertySetting/Sample/Utilities.swift create mode 100644 examples/BackgroundPropertySetting/Sample/ViewController.swift diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.pbxproj rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/AppDelegate.swift rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..90d6157f11 --- /dev/null +++ b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/DemoCellNode.swift rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/Info.plist rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/Utilities.swift rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/ViewController.swift rename to examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift diff --git a/examples/BackgroundPropertySetting/Sample.xcodeproj/project.pbxproj b/examples/BackgroundPropertySetting/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..297e538fbe --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,368 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13C5ECD49F2371E08EBD30F7 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B36F7F82AB7809F22F4C8635 /* Pods.framework */; }; + CCB8301E1C7688B500847D42 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCB8301D1C7688B500847D42 /* Assets.xcassets */; }; + CCB830211C7688B500847D42 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCB8301F1C7688B500847D42 /* LaunchScreen.storyboard */; }; + CCB8302C1C7688EC00847D42 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB830281C7688EC00847D42 /* AppDelegate.swift */; }; + CCB8302D1C7688EC00847D42 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB830291C7688EC00847D42 /* ViewController.swift */; }; + CCB8302E1C7688EC00847D42 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB8302A1C7688EC00847D42 /* Utilities.swift */; }; + CCB8302F1C7688EC00847D42 /* DemoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB8302B1C7688EC00847D42 /* DemoCellNode.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 811311717C2489CE07A1B9D0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + B36F7F82AB7809F22F4C8635 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BC1DAB40E0D262E674E35EEF /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + CCB830131C7688B500847D42 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + CCB8301D1C7688B500847D42 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + CCB830201C7688B500847D42 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + CCB830221C7688B500847D42 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CCB830281C7688EC00847D42 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + CCB830291C7688EC00847D42 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + CCB8302A1C7688EC00847D42 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + CCB8302B1C7688EC00847D42 /* DemoCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoCellNode.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CCB830101C7688B500847D42 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C5ECD49F2371E08EBD30F7 /* Pods.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2D17B5A68E217F63F504A0AB /* Pods */ = { + isa = PBXGroup; + children = ( + BC1DAB40E0D262E674E35EEF /* Pods.debug.xcconfig */, + 811311717C2489CE07A1B9D0 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 4C1A8C0BFF16C5457DF86019 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B36F7F82AB7809F22F4C8635 /* Pods.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CCB8300A1C7688B500847D42 = { + isa = PBXGroup; + children = ( + CCB830151C7688B500847D42 /* Sample */, + CCB830141C7688B500847D42 /* Products */, + 2D17B5A68E217F63F504A0AB /* Pods */, + 4C1A8C0BFF16C5457DF86019 /* Frameworks */, + ); + sourceTree = ""; + }; + CCB830141C7688B500847D42 /* Products */ = { + isa = PBXGroup; + children = ( + CCB830131C7688B500847D42 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + CCB830151C7688B500847D42 /* Sample */ = { + isa = PBXGroup; + children = ( + CCB830281C7688EC00847D42 /* AppDelegate.swift */, + CCB830291C7688EC00847D42 /* ViewController.swift */, + CCB8302A1C7688EC00847D42 /* Utilities.swift */, + CCB8302B1C7688EC00847D42 /* DemoCellNode.swift */, + CCB8301D1C7688B500847D42 /* Assets.xcassets */, + CCB8301F1C7688B500847D42 /* LaunchScreen.storyboard */, + CCB830221C7688B500847D42 /* Info.plist */, + ); + path = Sample; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CCB830121C7688B500847D42 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = CCB830251C7688B500847D42 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 95C31681ADDC41EDF46E7EDD /* Check Pods Manifest.lock */, + CCB8300F1C7688B500847D42 /* Sources */, + CCB830101C7688B500847D42 /* Frameworks */, + CCB830111C7688B500847D42 /* Resources */, + D7905F8C75C02D4B15A13055 /* Embed Pods Frameworks */, + FC69305B914BC44642AD442E /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = CCB830131C7688B500847D42 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CCB8300B1C7688B500847D42 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Adlai Holler"; + TargetAttributes = { + CCB830121C7688B500847D42 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = CCB8300E1C7688B500847D42 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CCB8300A1C7688B500847D42; + productRefGroup = CCB830141C7688B500847D42 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CCB830121C7688B500847D42 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CCB830111C7688B500847D42 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCB830211C7688B500847D42 /* LaunchScreen.storyboard in Resources */, + CCB8301E1C7688B500847D42 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 95C31681ADDC41EDF46E7EDD /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + D7905F8C75C02D4B15A13055 /* 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/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + FC69305B914BC44642AD442E /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CCB8300F1C7688B500847D42 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCB8302F1C7688EC00847D42 /* DemoCellNode.swift in Sources */, + CCB8302E1C7688EC00847D42 /* Utilities.swift in Sources */, + CCB8302D1C7688EC00847D42 /* ViewController.swift in Sources */, + CCB8302C1C7688EC00847D42 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + CCB8301F1C7688B500847D42 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CCB830201C7688B500847D42 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + CCB830231C7688B500847D42 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CCB830241C7688B500847D42 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CCB830261C7688B500847D42 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BC1DAB40E0D262E674E35EEF /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = adlai.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CCB830271C7688B500847D42 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 811311717C2489CE07A1B9D0 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = adlai.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CCB8300E1C7688B500847D42 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCB830231C7688B500847D42 /* Debug */, + CCB830241C7688B500847D42 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CCB830251C7688B500847D42 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCB830261C7688B500847D42 /* Debug */, + CCB830271C7688B500847D42 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CCB8300B1C7688B500847D42 /* Project object */; +} diff --git a/examples/BackgroundPropertySetting/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/BackgroundPropertySetting/Sample.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/BackgroundPropertySetting/Sample/AppDelegate.swift b/examples/BackgroundPropertySetting/Sample/AppDelegate.swift new file mode 100644 index 0000000000..ad9b18050a --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/AppDelegate.swift @@ -0,0 +1,50 @@ +// +// AppDelegate.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + let window = UIWindow(frame: UIScreen.mainScreen().bounds) + self.window = window + let vc = ViewController() + window.rootViewController = UINavigationController(rootViewController: vc) + window.makeKeyAndVisible() + return true + } + + func applicationWillResignActive(application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/examples/BackgroundPropertySetting/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/BackgroundPropertySetting/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard b/examples/BackgroundPropertySetting/Sample/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from examples/BackgroundPropertySetting/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard rename to examples/BackgroundPropertySetting/Sample/Base.lproj/LaunchScreen.storyboard diff --git a/examples/BackgroundPropertySetting/Sample/DemoCellNode.swift b/examples/BackgroundPropertySetting/Sample/DemoCellNode.swift new file mode 100644 index 0000000000..63524a540d --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/DemoCellNode.swift @@ -0,0 +1,87 @@ +// +// DemoCellNode.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit +import AsyncDisplayKit + +final class DemoCellNode: ASCellNode { + let childA = ASDisplayNode() + let childB = ASDisplayNode() + var state = State.Right + + override init() { + super.init() + usesImplicitHierarchyManagement = true + } + + override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec { + let specA = ASRatioLayoutSpec(ratio: 1, child: childA) + specA.flexBasis = ASRelativeDimensionMakeWithPoints(1) + specA.flexGrow = true + let specB = ASRatioLayoutSpec(ratio: 1, child: childB) + specB.flexBasis = ASRelativeDimensionMakeWithPoints(1) + specB.flexGrow = true + let children = state.isReverse ? [ specB, specA ] : [ specA, specB ] + let direction: ASStackLayoutDirection = state.isVertical ? .Vertical : .Horizontal + return ASStackLayoutSpec(direction: direction, + spacing: 20, + justifyContent: .SpaceAround, + alignItems: .Center, + children: children) + } + + override func animateLayoutTransition(context: ASContextTransitioning!) { + childA.frame = context.initialFrameForNode(childA) + childB.frame = context.initialFrameForNode(childB) + let tinyDelay = drand48() / 10 + UIView.animateWithDuration(0.5, delay: tinyDelay, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5, options: .BeginFromCurrentState, animations: { () -> Void in + self.childA.frame = context.finalFrameForNode(self.childA) + self.childB.frame = context.finalFrameForNode(self.childB) + }, completion: { + context.completeTransition($0) + }) + } + + enum State { + case Right + case Up + case Left + case Down + + var isVertical: Bool { + switch self { + case .Up, .Down: + return true + default: + return false + } + } + + var isReverse: Bool { + switch self { + case .Left, .Up: + return true + default: + return false + } + } + + mutating func advance() { + switch self { + case .Right: + self = .Up + case .Up: + self = .Left + case .Left: + self = .Down + case .Down: + self = .Right + } + } + } +} diff --git a/examples/BackgroundPropertySetting/Sample/Info.plist b/examples/BackgroundPropertySetting/Sample/Info.plist new file mode 100644 index 0000000000..61861abb1a --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/examples/BackgroundPropertySetting/Sample/Utilities.swift b/examples/BackgroundPropertySetting/Sample/Utilities.swift new file mode 100644 index 0000000000..5aedf39722 --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/Utilities.swift @@ -0,0 +1,15 @@ +// +// Utilities.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit + +extension UIColor { + static func random() -> UIColor { + return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0) + } +} diff --git a/examples/BackgroundPropertySetting/Sample/ViewController.swift b/examples/BackgroundPropertySetting/Sample/ViewController.swift new file mode 100644 index 0000000000..1573b9ebeb --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample/ViewController.swift @@ -0,0 +1,95 @@ +// +// ViewController.swift +// BackgroundPropertySetting +// +// Created by Adlai Holler on 2/17/16. +// Copyright © 2016 Adlai Holler. All rights reserved. +// + +import UIKit +import AsyncDisplayKit + +final class ViewController: ASViewController, ASCollectionDelegate, ASCollectionDataSource { + let itemCount = 1000 + + let itemSize: CGSize + let padding: CGFloat + var collectionNode: ASCollectionNode { + return node as! ASCollectionNode + } + + init() { + let layout = UICollectionViewFlowLayout() + (padding, itemSize) = ViewController.computeLayoutSizesForMainScreen() + layout.minimumInteritemSpacing = padding + layout.minimumLineSpacing = padding + super.init(node: ASCollectionNode(collectionViewLayout: layout)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Color", style: .Plain, target: self, action: "didTapColorsButton") + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Layout", style: .Plain, target: self, action: "didTapLayoutButton") + collectionNode.delegate = self + collectionNode.dataSource = self + title = "Background Updating" + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: ASCollectionDataSource + + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return itemCount + } + + func collectionView(collectionView: ASCollectionView, nodeBlockForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { + return { + let node = DemoCellNode() + node.backgroundColor = UIColor.random() + node.childA.backgroundColor = UIColor.random() + node.childB.backgroundColor = UIColor.random() + return node + } + } + + func collectionView(collectionView: ASCollectionView, constrainedSizeForNodeAtIndexPath indexPath: NSIndexPath) -> ASSizeRange { + return ASSizeRangeMake(itemSize, itemSize) + } + + // MARK: Action Handling + + @objc private func didTapColorsButton() { + let currentlyVisibleNodes = collectionNode.view.visibleNodes() + let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + dispatch_async(queue) { + for case let node as DemoCellNode in currentlyVisibleNodes { + node.backgroundColor = UIColor.random() + } + } + } + + @objc private func didTapLayoutButton() { + let currentlyVisibleNodes = collectionNode.view.visibleNodes() + let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + dispatch_async(queue) { + for case let node as DemoCellNode in currentlyVisibleNodes { + node.state.advance() + node.setNeedsLayout() + } + } + } + + // MARK: Static + + static func computeLayoutSizesForMainScreen() -> (padding: CGFloat, itemSize: CGSize) { + let numberOfColumns = 4 + let screen = UIScreen.mainScreen() + let scale = screen.scale + let screenWidth = Int(screen.bounds.width * screen.scale) + let itemWidthPx = (screenWidth - (numberOfColumns - 1)) / numberOfColumns + let leftover = screenWidth - itemWidthPx * numberOfColumns + let paddingPx = leftover / (numberOfColumns - 1) + let itemDimension = CGFloat(itemWidthPx) / scale + let padding = CGFloat(paddingPx) / scale + return (padding: padding, itemSize: CGSize(width: itemDimension, height: itemDimension)) + } +} From 56c74810291e3157a11a051ac11aba3cde527a0a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 15:24:31 -0800 Subject: [PATCH 192/224] [Example-BackgroundPropertySetting] Remove old files --- .../project.pbxproj | 365 ------------------ .../contents.xcworkspacedata | 7 - .../contents.xcworkspacedata | 10 - .../AppDelegate.swift | 50 --- .../AppIcon.appiconset/Contents.json | 38 -- .../Base.lproj/LaunchScreen.storyboard | 27 -- .../DemoCellNode.swift | 87 ----- .../Old/BackgroundPropertySetting/Info.plist | 36 -- .../BackgroundPropertySetting/Utilities.swift | 15 - .../ViewController.swift | 95 ----- 10 files changed, 730 deletions(-) delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift delete mode 100644 examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj deleted file mode 100644 index a90b8ebb7b..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.pbxproj +++ /dev/null @@ -1,365 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */; }; - CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736E1C751C8A00AB7199 /* ViewController.swift */; }; - CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCD373731C751C8A00AB7199 /* Assets.xcassets */; }; - CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */; }; - CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */; }; - CCD373811C75228900AB7199 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD373801C75228900AB7199 /* Utilities.swift */; }; - FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D966CA4D089E4178A58E447C /* Pods.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; - 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; - CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BackgroundPropertySetting.app; sourceTree = BUILT_PRODUCTS_DIR; }; - CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - CCD3736E1C751C8A00AB7199 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - CCD373731C751C8A00AB7199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - CCD373761C751C8A00AB7199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - CCD373781C751C8A00AB7199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoCellNode.swift; sourceTree = ""; }; - CCD373801C75228900AB7199 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; - D966CA4D089E4178A58E447C /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - CCD373661C751C8A00AB7199 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 30042A321E6A9E291E310396 /* Frameworks */ = { - isa = PBXGroup; - children = ( - D966CA4D089E4178A58E447C /* Pods.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9C878D1471151DC350759386 /* Pods */ = { - isa = PBXGroup; - children = ( - 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */, - 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - CCD373601C751C8A00AB7199 = { - isa = PBXGroup; - children = ( - CCD3736B1C751C8A00AB7199 /* BackgroundPropertySetting */, - CCD3736A1C751C8A00AB7199 /* Products */, - 9C878D1471151DC350759386 /* Pods */, - 30042A321E6A9E291E310396 /* Frameworks */, - ); - sourceTree = ""; - }; - CCD3736A1C751C8A00AB7199 /* Products */ = { - isa = PBXGroup; - children = ( - CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */, - ); - name = Products; - sourceTree = ""; - }; - CCD3736B1C751C8A00AB7199 /* BackgroundPropertySetting */ = { - isa = PBXGroup; - children = ( - CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */, - CCD3736E1C751C8A00AB7199 /* ViewController.swift */, - CCD373801C75228900AB7199 /* Utilities.swift */, - CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */, - CCD373731C751C8A00AB7199 /* Assets.xcassets */, - CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */, - CCD373781C751C8A00AB7199 /* Info.plist */, - ); - path = BackgroundPropertySetting; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - CCD373681C751C8A00AB7199 /* BackgroundPropertySetting */ = { - isa = PBXNativeTarget; - buildConfigurationList = CCD3737B1C751C8A00AB7199 /* Build configuration list for PBXNativeTarget "BackgroundPropertySetting" */; - buildPhases = ( - 8DB9E5B4CBB8681DB7A8C0F6 /* Check Pods Manifest.lock */, - CCD373651C751C8A00AB7199 /* Sources */, - CCD373661C751C8A00AB7199 /* Frameworks */, - CCD373671C751C8A00AB7199 /* Resources */, - EDCBBE7D585831EB6D1AAC3F /* Embed Pods Frameworks */, - 34D9C63D3C5940DDB4DFF82C /* Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BackgroundPropertySetting; - productName = BackgroundPropertySetting; - productReference = CCD373691C751C8A00AB7199 /* BackgroundPropertySetting.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - CCD373611C751C8A00AB7199 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; - ORGANIZATIONNAME = "Adlai Holler"; - TargetAttributes = { - CCD373681C751C8A00AB7199 = { - CreatedOnToolsVersion = 7.2.1; - }; - }; - }; - buildConfigurationList = CCD373641C751C8A00AB7199 /* Build configuration list for PBXProject "BackgroundPropertySetting" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = CCD373601C751C8A00AB7199; - productRefGroup = CCD3736A1C751C8A00AB7199 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - CCD373681C751C8A00AB7199 /* BackgroundPropertySetting */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - CCD373671C751C8A00AB7199 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */, - CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 34D9C63D3C5940DDB4DFF82C /* Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 8DB9E5B4CBB8681DB7A8C0F6 /* Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - EDCBBE7D585831EB6D1AAC3F /* 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/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - CCD373651C751C8A00AB7199 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */, - CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */, - CCD373811C75228900AB7199 /* Utilities.swift in Sources */, - CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - CCD373761C751C8A00AB7199 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - CCD373791C751C8A00AB7199 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - CCD3737A1C751C8A00AB7199 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CCD3737C1C751C8A00AB7199 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4143292D7932CD5F37CA510D /* Pods.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = BackgroundPropertySetting/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = adlai.BackgroundPropertySetting; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - CCD3737D1C751C8A00AB7199 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 842A7A17DB7976736CF93CDE /* Pods.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = BackgroundPropertySetting/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = adlai.BackgroundPropertySetting; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - CCD373641C751C8A00AB7199 /* Build configuration list for PBXProject "BackgroundPropertySetting" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CCD373791C751C8A00AB7199 /* Debug */, - CCD3737A1C751C8A00AB7199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CCD3737B1C751C8A00AB7199 /* Build configuration list for PBXNativeTarget "BackgroundPropertySetting" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CCD3737C1C751C8A00AB7199 /* Debug */, - CCD3737D1C751C8A00AB7199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = CCD373611C751C8A00AB7199 /* Project object */; -} diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 095df1d899..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7f92fa35eb..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift deleted file mode 100644 index ad9b18050a..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/AppDelegate.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// AppDelegate.swift -// BackgroundPropertySetting -// -// Created by Adlai Holler on 2/17/16. -// Copyright © 2016 Adlai Holler. All rights reserved. -// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - let window = UIWindow(frame: UIScreen.mainScreen().bounds) - self.window = window - let vc = ViewController() - window.rootViewController = UINavigationController(rootViewController: vc) - window.makeKeyAndVisible() - return true - } - - func applicationWillResignActive(application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - -} - diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 118c98f746..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 90d6157f11..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift deleted file mode 100644 index 63524a540d..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/DemoCellNode.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// DemoCellNode.swift -// BackgroundPropertySetting -// -// Created by Adlai Holler on 2/17/16. -// Copyright © 2016 Adlai Holler. All rights reserved. -// - -import UIKit -import AsyncDisplayKit - -final class DemoCellNode: ASCellNode { - let childA = ASDisplayNode() - let childB = ASDisplayNode() - var state = State.Right - - override init() { - super.init() - usesImplicitHierarchyManagement = true - } - - override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec { - let specA = ASRatioLayoutSpec(ratio: 1, child: childA) - specA.flexBasis = ASRelativeDimensionMakeWithPoints(1) - specA.flexGrow = true - let specB = ASRatioLayoutSpec(ratio: 1, child: childB) - specB.flexBasis = ASRelativeDimensionMakeWithPoints(1) - specB.flexGrow = true - let children = state.isReverse ? [ specB, specA ] : [ specA, specB ] - let direction: ASStackLayoutDirection = state.isVertical ? .Vertical : .Horizontal - return ASStackLayoutSpec(direction: direction, - spacing: 20, - justifyContent: .SpaceAround, - alignItems: .Center, - children: children) - } - - override func animateLayoutTransition(context: ASContextTransitioning!) { - childA.frame = context.initialFrameForNode(childA) - childB.frame = context.initialFrameForNode(childB) - let tinyDelay = drand48() / 10 - UIView.animateWithDuration(0.5, delay: tinyDelay, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5, options: .BeginFromCurrentState, animations: { () -> Void in - self.childA.frame = context.finalFrameForNode(self.childA) - self.childB.frame = context.finalFrameForNode(self.childB) - }, completion: { - context.completeTransition($0) - }) - } - - enum State { - case Right - case Up - case Left - case Down - - var isVertical: Bool { - switch self { - case .Up, .Down: - return true - default: - return false - } - } - - var isReverse: Bool { - switch self { - case .Left, .Up: - return true - default: - return false - } - } - - mutating func advance() { - switch self { - case .Right: - self = .Up - case .Up: - self = .Left - case .Left: - self = .Down - case .Down: - self = .Right - } - } - } -} diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist deleted file mode 100644 index 61861abb1a..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - - diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift deleted file mode 100644 index 5aedf39722..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/Utilities.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Utilities.swift -// BackgroundPropertySetting -// -// Created by Adlai Holler on 2/17/16. -// Copyright © 2016 Adlai Holler. All rights reserved. -// - -import UIKit - -extension UIColor { - static func random() -> UIColor { - return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0) - } -} diff --git a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift b/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift deleted file mode 100644 index 1573b9ebeb..0000000000 --- a/examples/BackgroundPropertySetting/Old/BackgroundPropertySetting/ViewController.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ViewController.swift -// BackgroundPropertySetting -// -// Created by Adlai Holler on 2/17/16. -// Copyright © 2016 Adlai Holler. All rights reserved. -// - -import UIKit -import AsyncDisplayKit - -final class ViewController: ASViewController, ASCollectionDelegate, ASCollectionDataSource { - let itemCount = 1000 - - let itemSize: CGSize - let padding: CGFloat - var collectionNode: ASCollectionNode { - return node as! ASCollectionNode - } - - init() { - let layout = UICollectionViewFlowLayout() - (padding, itemSize) = ViewController.computeLayoutSizesForMainScreen() - layout.minimumInteritemSpacing = padding - layout.minimumLineSpacing = padding - super.init(node: ASCollectionNode(collectionViewLayout: layout)) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Color", style: .Plain, target: self, action: "didTapColorsButton") - navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Layout", style: .Plain, target: self, action: "didTapLayoutButton") - collectionNode.delegate = self - collectionNode.dataSource = self - title = "Background Updating" - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: ASCollectionDataSource - - func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return itemCount - } - - func collectionView(collectionView: ASCollectionView, nodeBlockForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { - return { - let node = DemoCellNode() - node.backgroundColor = UIColor.random() - node.childA.backgroundColor = UIColor.random() - node.childB.backgroundColor = UIColor.random() - return node - } - } - - func collectionView(collectionView: ASCollectionView, constrainedSizeForNodeAtIndexPath indexPath: NSIndexPath) -> ASSizeRange { - return ASSizeRangeMake(itemSize, itemSize) - } - - // MARK: Action Handling - - @objc private func didTapColorsButton() { - let currentlyVisibleNodes = collectionNode.view.visibleNodes() - let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) - dispatch_async(queue) { - for case let node as DemoCellNode in currentlyVisibleNodes { - node.backgroundColor = UIColor.random() - } - } - } - - @objc private func didTapLayoutButton() { - let currentlyVisibleNodes = collectionNode.view.visibleNodes() - let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) - dispatch_async(queue) { - for case let node as DemoCellNode in currentlyVisibleNodes { - node.state.advance() - node.setNeedsLayout() - } - } - } - - // MARK: Static - - static func computeLayoutSizesForMainScreen() -> (padding: CGFloat, itemSize: CGSize) { - let numberOfColumns = 4 - let screen = UIScreen.mainScreen() - let scale = screen.scale - let screenWidth = Int(screen.bounds.width * screen.scale) - let itemWidthPx = (screenWidth - (numberOfColumns - 1)) / numberOfColumns - let leftover = screenWidth - itemWidthPx * numberOfColumns - let paddingPx = leftover / (numberOfColumns - 1) - let itemDimension = CGFloat(itemWidthPx) / scale - let padding = CGFloat(paddingPx) / scale - return (padding: padding, itemSize: CGSize(width: itemDimension, height: itemDimension)) - } -} From 311c375c4b3d0a28b48aed7a8edc0b79258a9aa0 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 17:53:51 -0800 Subject: [PATCH 193/224] [Example-BackgroundPropertySetting] Make Sample scheme shared so hopefully the CI build works --- .../xcshareddata/xcschemes/Sample.xcscheme | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 examples/BackgroundPropertySetting/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme diff --git a/examples/BackgroundPropertySetting/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/BackgroundPropertySetting/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..58a9f2df4d --- /dev/null +++ b/examples/BackgroundPropertySetting/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0af95c344c8516ec3fab12f0f2e5cae18a55d42f Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Feb 2016 18:12:40 -0800 Subject: [PATCH 194/224] [ASBridgedPropertiesTest] Make node retention test more reliable. --- .../ASBridgedPropertiesTests.mm | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm index 5ec77c5dcb..0a9ccd89dd 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -32,6 +32,18 @@ @end +@interface ASBridgedPropertiesTestNode : ASDisplayNode +@property (nullable, nonatomic, copy) dispatch_block_t onDealloc; +@end + +@implementation ASBridgedPropertiesTestNode + +- (void)dealloc { + _onDealloc(); +} + +@end + @interface ASBridgedPropertiesTests : XCTestCase @end @@ -60,10 +72,12 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { - (void)testThatDirtyNodesAreNotRetained { ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; - __weak ASDisplayNode *weakNode = nil; + __block BOOL didDealloc = NO; @autoreleasepool { - __attribute__((objc_precise_lifetime)) ASDisplayNode *node = [ASDisplayNode new]; - weakNode = node; + __attribute__((objc_precise_lifetime)) ASBridgedPropertiesTestNode *node = [ASBridgedPropertiesTestNode new]; + node.onDealloc = ^{ + didDealloc = YES; + }; [node view]; XCTAssertEqual(node.alpha, 1); ASDispatchSyncOnOtherThread(^{ @@ -71,9 +85,8 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { }); XCTAssertEqual(node.alpha, 1); XCTAssert(ctrl.test_isFlushScheduled); - XCTAssertNotNil(weakNode); } - XCTAssertNil(weakNode); + XCTAssertTrue(didDealloc); } - (void)testThatSettingABridgedViewPropertyInBackgroundGetsFlushedOnNextRunLoop From a34f5219b610d7aa92f9bf2997cbe161ca48d34a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 19 Feb 2016 12:00:33 -0800 Subject: [PATCH 195/224] [ASBridgedPropertiesTests] Disable troublesome node deallocation test --- AsyncDisplayKitTests/ASBridgedPropertiesTests.mm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm index 0a9ccd89dd..cac9f5f59f 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -69,7 +69,11 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { XCTAssertNotNil([ASPendingStateController sharedInstance]); } -- (void)testThatDirtyNodesAreNotRetained +/// FIXME: This test is unreliable for an as-yet unknown reason +/// but that being intermittent, and this test being so strict, it's +/// reasonable to assume for now the failures don't reflect a framework bug. +/// See https://github.com/facebook/AsyncDisplayKit/pull/1048 +- (void)DISABLED_testThatDirtyNodesAreNotRetained { ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; __block BOOL didDealloc = NO; From 12d5c73325f488196ec350f44dff234e5eceba15 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:22:39 -0800 Subject: [PATCH 196/224] [ASTextNode] Remove thread affinity dispatch --- AsyncDisplayKit/ASTextNode.mm | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3c0d7797b0..d333d88b06 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -366,22 +366,20 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // We need an entirely new renderer [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - // Tell the display node superclasses that the cached layout is incorrect now - [self invalidateCalculatedLayout]; + // Tell the display node superclasses that the cached layout is incorrect now + [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; + [self setNeedsDisplay]; - self.accessibilityLabel = _attributedString.string; + self.accessibilityLabel = _attributedString.string; + + if (_attributedString.length == 0) { + // We're not an accessibility element by default if there is no string. + self.isAccessibilityElement = NO; + } else { + self.isAccessibilityElement = YES; + } - if (_attributedString.length == 0) { - // We're not an accessibility element by default if there is no string. - self.isAccessibilityElement = NO; - } else { - self.isAccessibilityElement = YES; - } - }); - // reset the scale factor if we get a new string. _currentScaleFactor = 0; if (attributedString.length > 0) { From d9fc11f0f20fadfbec894c5234f2299c0b5c602a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:23:01 -0800 Subject: [PATCH 197/224] [ASDisplayNode] Remove thread affinity assertion in -shouldRasterizeDescendants --- AsyncDisplayKit/ASDisplayNode.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index d68ca31a70..de1612ab38 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -905,7 +905,6 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( - (BOOL)shouldRasterizeDescendants { - ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); From 2a94d88b4bdf95e30fcd7df05cb1915353ab33c0 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:26:09 -0800 Subject: [PATCH 198/224] [ASImageNode] Remove thread affinity dispatches --- AsyncDisplayKit/ASImageNode.mm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 87e6a02427..2985e0f370 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -123,11 +123,8 @@ if (!ASObjectIsEqual(_image, image)) { _image = image; - ASDN::MutexUnlocker u(_imageLock); - ASPerformBlockOnMainThread(^{ - [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; - }); + [self invalidateCalculatedLayout]; + [self setNeedsDisplay]; } } From b47c2d5ecf559b3e4a381b2641374e18c8998397 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:26:26 -0800 Subject: [PATCH 199/224] [ASTextNode] Remove more thread affinity dispatches --- AsyncDisplayKit/ASTextNode.mm | 42 +++++++++-------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index d333d88b06..f78d929009 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -333,10 +333,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // Instead of invalidating the renderer, in case this is a new call with a different constrained size, // just update the size of the NSTextContainer that is owned by the renderer's internal context object. [self _renderer].constrainedSize = _constrainedSize; - - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + + [self setNeedsDisplay]; CGSize size = [[self _renderer] size]; // the renderer computes the current scale factor during sizing, so let's grab it here @@ -400,9 +398,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _exclusionPaths = [exclusionPaths copy]; [self _invalidateRenderer]; [self invalidateCalculatedLayout]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } - (NSArray *)exclusionPaths @@ -951,9 +947,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } _shadowColor = shadowColor; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -967,9 +961,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { _shadowOffset = shadowOffset; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -983,9 +975,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (_shadowOpacity != shadowOpacity) { _shadowOpacity = shadowOpacity; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -999,9 +989,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (_shadowRadius != shadowRadius) { _shadowRadius = shadowRadius; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -1053,9 +1041,7 @@ static NSAttributedString *DefaultTruncationAttributedString() if (_truncationMode != truncationMode) { _truncationMode = truncationMode; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -1070,9 +1056,7 @@ static NSAttributedString *DefaultTruncationAttributedString() if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { _pointSizeScaleFactors = pointSizeScaleFactors; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; }} - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines @@ -1080,9 +1064,7 @@ static NSAttributedString *DefaultTruncationAttributedString() if (_maximumNumberOfLines != maximumNumberOfLines) { _maximumNumberOfLines = maximumNumberOfLines; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } } @@ -1102,9 +1084,7 @@ static NSAttributedString *DefaultTruncationAttributedString() { [self _updateComposedTruncationString]; [self _invalidateRenderer]; - ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ - [self setNeedsDisplay]; - }); + [self setNeedsDisplay]; } /** From edf6ee59e83d1a4dba57fa41cfee1f813b4ea7d7 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:32:04 -0800 Subject: [PATCH 200/224] [ASDisplayNode] Remove RespectThreadAffinityOfNode function --- AsyncDisplayKit/ASDisplayNode.mm | 20 ------------------- .../Private/ASDisplayNodeInternal.h | 1 - 2 files changed, 21 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index de1612ab38..906cfc43fa 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -79,26 +79,6 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } -void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()) -{ - ASDisplayNodeCAssertNotNil(block, @"block is required"); - if (!block) { - return; - } - - { - // Hold the lock to avoid a race where the node gets loaded while the block is in-flight. - ASDN::MutexLocker l(node->_propertyLock); - if (node.nodeLoaded) { - ASPerformBlockOnMainThread(^{ - block(); - }); - } else { - block(); - } - } -} - /** * Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL. * diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index bc53ff77c0..67d0dea9e8 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -25,7 +25,6 @@ @class _ASDisplayLayer; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { From b929bfdd338712a56805572d44e07b631628199d Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:37:59 -0800 Subject: [PATCH 201/224] I am not a smart man --- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 8423ed0fac..2593c42151 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -309,6 +309,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie _bridge_prologue_write; if (self.nodeLoaded) { if (ASDisplayNodeThreadIsMain()) { + [self __setNeedsLayout]; _messageToViewOrLayer(setNeedsLayout); } else { if (!_pendingViewState.hasChanges) { From e244b10e1e530e2e907af507c1e8254b069b22e1 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 20 Feb 2016 12:44:04 -0800 Subject: [PATCH 202/224] Add a note about order of operations issue with frame/bounds/position --- AsyncDisplayKit/Private/_ASPendingState.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index 007eeedad4..2e161034bc 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -810,6 +810,9 @@ static UIColor *defaultTintColor = nil; if (flags.setAccessibilityIdentifier) view.accessibilityIdentifier = accessibilityIdentifier; + // FIXME: How should we reconcile order-of-operations between setting frame, bounds, position? + // Note we can't read bounds and position in the background, so we have to keep the frame + // value intact until application time (now). if (flags.setFrame) { if (setFrameDirectly) { // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: From 6d9720bd18a320cfd20732e52ca50bc373d36416 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Sat, 20 Feb 2016 17:33:29 -0800 Subject: [PATCH 203/224] Wrong name in header --- AsyncDisplayKit/Layout/ASRelativeSize.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.h b/AsyncDisplayKit/Layout/ASRelativeSize.h index db5d662a9b..1f08e92f56 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.h +++ b/AsyncDisplayKit/Layout/ASRelativeSize.h @@ -44,7 +44,7 @@ extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDi extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size); /** Resolve this relative size relative to a parent size. */ -extern CGSize ASRelativeSizeResolve(ASRelativeSize relativeSize, CGSize parentSize); +extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize); extern BOOL ASRelativeSizeEqualToRelativeSize(ASRelativeSize lhs, ASRelativeSize rhs); From 4637bf6a373c15036914c0caf370f200c724f561 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 20 Feb 2016 17:50:19 -0800 Subject: [PATCH 204/224] [ASStackLayoutSpec] Micro-optimizations to ultrahot codepaths, reducing both locking overhead and method calls. --- .../Private/ASStackUnpositionedLayout.mm | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index ab2eb3aac2..a19ae298ba 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -76,16 +76,19 @@ static void stretchChildrenAlongCrossDimension(std::vectorlayout.size); for (auto &l : layouts) { - const ASStackLayoutAlignItems alignItems = alignment(l.child.alignSelf, style.alignItems); + const id child = l.child; + const CGSize size = l.layout.size; + + const ASStackLayoutAlignItems alignItems = alignment(child.alignSelf, style.alignItems); - const CGFloat cross = crossDimension(style.direction, l.layout.size); - const CGFloat stack = stackDimension(style.direction, l.layout.size); + const CGFloat cross = crossDimension(style.direction, size); + const CGFloat stack = stackDimension(style.direction, size); // restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax, // not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!) // they are forced to choose the same width as all the other children. if (alignItems == ASStackLayoutAlignItemsStretch && fabs(cross - childCrossMax) > 0.01) { - l.layout = crossChildLayout(l.child, style, stack, stack, childCrossMax, childCrossMax); + l.layout = crossChildLayout(child, style, stack, stack, childCrossMax, childCrossMax); } } } @@ -112,7 +115,9 @@ static CGFloat computeStackDimensionSum(const std::vector child = l.child; + const ASLayoutOptions *layoutOptions = child.layoutOptions; + return x + layoutOptions.spacingBefore + layoutOptions.spacingAfter; }); // Sum up the childrens' dimensions (including spacing) in the stack direction. @@ -215,8 +220,9 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector child = item.child; + if (isFlexibleInBothDirections(child)) { + item.layout = crossChildLayout(child, style, 0, 0, @@ -294,8 +300,9 @@ static std::vector layoutChildrenAlongUnconstrainedStac const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); return AS::map(children, [&](id child) -> ASStackUnpositionedItem { - const BOOL isUnconstrainedFlexBasis = ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimensionUnconstrained, child.flexBasis); - const CGFloat exactStackDimension = ASRelativeDimensionResolve(child.flexBasis, stackDimension(style.direction, size)); + const ASRelativeDimension flexBasis = child.flexBasis; + const BOOL isUnconstrainedFlexBasis = ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimensionUnconstrained, flexBasis); + const CGFloat exactStackDimension = ASRelativeDimensionResolve(flexBasis, stackDimension(style.direction, size)); if (useOptimizedFlexing && isFlexibleInBothDirections(child)) { return { child, [ASLayout layoutWithLayoutableObject:child size:{0, 0}] }; From 648dc817ad41e2d5b96fdda138920666052d2233 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 10:03:06 -0800 Subject: [PATCH 205/224] [ASDisplayNode] Use a C function to lazily create pending view state --- AsyncDisplayKit/ASDisplayNode.mm | 12 +++++- .../Private/ASDisplayNode+UIViewBridge.mm | 42 ++++++++++--------- .../Private/ASDisplayNodeInternal.h | 8 ++-- .../ASBridgedPropertiesTests.mm | 4 +- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 906cfc43fa..4d46d80681 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -79,6 +79,17 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } +_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) +{ + ASDN::MutexLocker l(node->_propertyLock); + _ASPendingState *result = node->_pendingViewState; + if (result == nil) { + result = [[_ASPendingState alloc] init]; + node->_pendingViewState = result; + } + return result; +} + /** * Returns ASDisplayNodeFlags for the givern class/instance. instance MAY BE NIL. * @@ -257,7 +268,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _contentsScaleForDisplay = ASScreenScale(); _displaySentinel = [[ASSentinel alloc] init]; _preferredFrameSize = CGSizeZero; - _pendingViewState = [_ASPendingState new]; } - (id)init diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 2593c42151..97e85c9cff 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -64,25 +64,23 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo #define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \ (_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\ - : self.pendingViewState.viewAndPendingViewStateProperty + : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty #define _setToViewOrLayer(layerProperty, layerValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ - if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } + if (shouldApply) { (_view ? _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr) : _layer.layerProperty = (layerValueExpr)); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } #define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ -if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { _pendingViewState.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } +if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } -#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : self.pendingViewState.viewAndPendingViewStateProperty +#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty -#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : self.pendingViewState.layerProperty +#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : ASDisplayNodeGetPendingState(self).layerProperty #define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ -if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingViewState.layerProperty = (layerValueExpr); } +if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); } #define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector]) -#define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector] - /** * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. @@ -261,10 +259,11 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie self.bounds = bounds; self.position = position; } else if (nodeLoaded && !isMainThread) { - if (!_pendingViewState.hasChanges) { - [ASPendingStateController.sharedInstance registerNode:self]; + _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); + if (!pendingState.hasChanges) { + [[ASPendingStateController sharedInstance] registerNode:self]; } - _pendingViewState.frame = rect; + pendingState.frame = rect; } } @@ -292,11 +291,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie if (ASDisplayNodeThreadIsMain()) { _messageToViewOrLayer(setNeedsDisplay); } else { - if (!_pendingViewState.hasChanges) { - [ASPendingStateController.sharedInstance registerNode:self]; + _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); + if (!pendingState.hasChanges) { + [[ASPendingStateController sharedInstance] registerNode:self]; } [self __setNeedsDisplay]; - [_pendingViewState setNeedsDisplay]; + [pendingState setNeedsDisplay]; } } else { [self __setNeedsDisplay]; @@ -312,13 +312,14 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie [self __setNeedsLayout]; _messageToViewOrLayer(setNeedsLayout); } else { - if (!_pendingViewState.hasChanges) { - [ASPendingStateController.sharedInstance registerNode:self]; + _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); + if (!pendingState.hasChanges) { + [[ASPendingStateController sharedInstance] registerNode:self]; } // NOTE: We will call [self __setNeedsLayout] just before we apply // the pending state. We need to call it on main if the node is loaded // to support implicit hierarchy management. - [_pendingViewState setNeedsLayout]; + [pendingState setNeedsLayout]; } } else { [self __setNeedsLayout]; @@ -511,21 +512,22 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie return _view.contentMode; } } else { - return self.pendingViewState.contentMode; + return ASDisplayNodeGetPendingState(self).contentMode; } } - (void)setContentMode:(UIViewContentMode)contentMode { _bridge_prologue_write; - if (__loaded) { + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { if (_flags.layerBacked) { _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); } else { _view.contentMode = contentMode; } } else { - self.pendingViewState.contentMode = contentMode; + ASDisplayNodeGetPendingState(self).contentMode = contentMode; } } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 67d0dea9e8..14f2f8a889 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -23,9 +23,13 @@ @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; +@class _ASPendingState; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); +/// Get the pending view state for the node, creating one if needed. +_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node); + typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { ASDisplayNodeMethodOverrideNone = 0, @@ -36,7 +40,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 }; -@class _ASPendingState; @class _ASDisplayNodePosition; FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; @@ -139,9 +142,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // The _ASDisplayLayer backing the node, if any. @property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer; -// Creates a pendingViewState if one doesn't exist. Allows setting view properties on a bg thread before there is a view. -@property (atomic, retain, readonly) _ASPendingState *pendingViewState; - // Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; diff --git a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm index cac9f5f59f..c95ec00542 100644 --- a/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm +++ b/AsyncDisplayKitTests/ASBridgedPropertiesTests.mm @@ -168,13 +168,13 @@ static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) { { ASPendingStateController *ctrl = [ASPendingStateController sharedInstance]; ASDisplayNode *node = [ASDisplayNode new]; - XCTAssertFalse(node.pendingViewState.hasChanges); + XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges); [node view]; XCTAssertEqual(node.alpha, 1); node.alpha = 0; XCTAssertEqual(node.view.alpha, 0); XCTAssertEqual(node.alpha, 0); - XCTAssertFalse(node.pendingViewState.hasChanges); + XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges); XCTAssertFalse(ctrl.test_isFlushScheduled); } From e1bf0f6a88ab6d1f1d56297423c3383f6fcd42b4 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 10:08:27 -0800 Subject: [PATCH 206/224] [ASDisplayNode:setFrame] Initialize local variable values --- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 97e85c9cff..41a315ef23 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -252,8 +252,8 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo * This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call. * A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set. */ - CGRect bounds; - CGPoint position; + CGRect bounds = CGRectZero; + CGPoint position = CGPointZero; ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position); self.bounds = bounds; From 3ff833c4f52010c2ab97e5b52f9142f8339f02b2 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 10:10:04 -0800 Subject: [PATCH 207/224] [ASDisplayNode:UIViewBridge] Reduce calls to -isNodeLoaded --- .../Private/ASDisplayNode+UIViewBridge.mm | 8 +-- .../Private/ASDisplayNodeInternal.h | 56 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 41a315ef23..4a5c02fd97 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -37,7 +37,7 @@ #define DISPLAYNODE_USE_LOCKS 1 -#define __loaded (_layer != nil) +#define __loaded (_view != nil || (_layer != nil && _flags.layerBacked)) #if DISPLAYNODE_USE_LOCKS #define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self) @@ -53,10 +53,10 @@ /// This function must be called with the node's lock already held (after _bridge_prologue_write). ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) { if (ASDisplayNodeThreadIsMain()) { - return node.nodeLoaded; + return (node->_view != nil || (node->_layer != nil && node->_flags.layerBacked)); } else { if (node.nodeLoaded && !node->_pendingViewState.hasChanges) { - [ASPendingStateController.sharedInstance registerNode:node]; + [[ASPendingStateController sharedInstance] registerNode:node]; } return NO; } @@ -235,7 +235,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo _bridge_prologue_write; BOOL setFrameDirectly = _flags.synchronous && !_flags.layerBacked; BOOL isMainThread = ASDisplayNodeThreadIsMain(); - BOOL nodeLoaded = self.nodeLoaded; + BOOL nodeLoaded = __loaded; if (nodeLoaded && isMainThread && setFrameDirectly) { // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 14f2f8a889..8a3aa97b80 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -55,10 +55,37 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @package _ASPendingState *_pendingViewState; -@protected // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. ASDN::RecursiveMutex _propertyLock; + UIView *_view; + CALayer *_layer; + struct ASDisplayNodeFlags { + // public properties + unsigned synchronous:1; + unsigned layerBacked:1; + unsigned displaysAsynchronously:1; + unsigned shouldRasterizeDescendants:1; + unsigned shouldBypassEnsureDisplay:1; + unsigned displaySuspended:1; + unsigned hasCustomDrawingPriority:1; + + // whether custom drawing is enabled + unsigned implementsInstanceDrawRect:1; + unsigned implementsDrawRect:1; + unsigned implementsInstanceImageDisplay:1; + unsigned implementsImageDisplay:1; + unsigned implementsDrawParameters:1; + + // internal state + unsigned isMeasured:1; + unsigned isEnteringHierarchy:1; + unsigned isExitingHierarchy:1; + unsigned isInHierarchy:1; + unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS; + } _flags; + +@protected ASDisplayNode * __weak _supernode; ASSentinel *_displaySentinel; @@ -89,39 +116,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo ASDisplayNodeDidLoadBlock _nodeLoadedBlock; Class _viewClass; Class _layerClass; - UIView *_view; - CALayer *_layer; UIImage *_placeholderImage; CALayer *_placeholderLayer; // keeps track of nodes/subnodes that have not finished display, used with placeholders NSMutableSet *_pendingDisplayNodes; - - struct ASDisplayNodeFlags { - // public properties - unsigned synchronous:1; - unsigned layerBacked:1; - unsigned displaysAsynchronously:1; - unsigned shouldRasterizeDescendants:1; - unsigned shouldBypassEnsureDisplay:1; - unsigned displaySuspended:1; - unsigned hasCustomDrawingPriority:1; - - // whether custom drawing is enabled - unsigned implementsInstanceDrawRect:1; - unsigned implementsDrawRect:1; - unsigned implementsInstanceImageDisplay:1; - unsigned implementsImageDisplay:1; - unsigned implementsDrawParameters:1; - - // internal state - unsigned isMeasured:1; - unsigned isEnteringHierarchy:1; - unsigned isExitingHierarchy:1; - unsigned isInHierarchy:1; - unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS; - } _flags; ASDisplayNodeExtraIvars _extra; From b1c7f47b022ff78eeceaf2df52ef3d5680644ddc Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 10:42:38 -0800 Subject: [PATCH 208/224] [ASDisplayNode:UIViewBridge] Refactor setFrame: to simplify logic --- .../Private/ASDisplayNode+UIViewBridge.mm | 74 +++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 4a5c02fd97..6fbe499220 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -233,37 +233,53 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (void)setFrame:(CGRect)rect { _bridge_prologue_write; - BOOL setFrameDirectly = _flags.synchronous && !_flags.layerBacked; - BOOL isMainThread = ASDisplayNodeThreadIsMain(); - BOOL nodeLoaded = __loaded; - if (nodeLoaded && isMainThread && setFrameDirectly) { - // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: - - // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform -#if DEBUG - // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); -#endif - _view.frame = rect; - } else if (!nodeLoaded || (isMainThread && !setFrameDirectly)) { - /** - * Sets a new frame to this node by changing its bounds and position. This method can be safely called even if - * the transform is a non-identity transform, because bounds and position can be set instead of frame. - * This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call. - * A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set. - */ - CGRect bounds = CGRectZero; - CGPoint position = CGPointZero; - ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position); - self.bounds = bounds; - self.position = position; - } else if (nodeLoaded && !isMainThread) { - _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); - if (!pendingState.hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:self]; + // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: + struct ASDisplayNodeFlags flags = _flags; + BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked; + + BOOL nodeLoaded = __loaded; + BOOL isMainThread = ASDisplayNodeThreadIsMain(); + if (!setFrameDirectly) { + BOOL canReadProperties = isMainThread || !nodeLoaded; + if (canReadProperties) { + // We don't have to set frame directly, and we can read current properties. + // Compute a new bounds and position and set them on self. + CGRect bounds = CGRectZero; + CGPoint position = CGPointZero; + ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position); + + self.bounds = bounds; + self.position = position; + } else { + // We don't have to set frame directly, but we can't read properties. + // Store the frame in our pending state, and it'll get decomposed into + // bounds and position when the pending state is applied. + _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); + if (nodeLoaded && !pendingState.hasChanges) { + [[ASPendingStateController sharedInstance] registerNode:self]; + } + pendingState.frame = rect; + } + } else { + if (nodeLoaded && isMainThread) { + // We do have to set frame directly, and we're on main thread with a loaded node. + // Just set the frame on the view. + // NOTE: Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform. +#if DEBUG + // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. + ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); +#endif + _view.frame = rect; + } else { + // We do have to set frame directly, but either the node isn't loaded or we're on a non-main thread. + // Set the frame on the pending state, and it'll call setFrame: when applied. + _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); + if (nodeLoaded && !pendingState.hasChanges) { + [[ASPendingStateController sharedInstance] registerNode:self]; + } + pendingState.frame = rect; } - pendingState.frame = rect; } } From d9d4d40997646f7d10afe77575584016364092af Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 10:52:20 -0800 Subject: [PATCH 209/224] [ASDisplayNode:UIViewBridge:setFrame] Use CALayer directly when possible --- .../Private/ASDisplayNode+UIViewBridge.mm | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 6fbe499220..b8dd56894f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -245,12 +245,22 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo if (canReadProperties) { // We don't have to set frame directly, and we can read current properties. // Compute a new bounds and position and set them on self. - CGRect bounds = CGRectZero; - CGPoint position = CGPointZero; - ASBoundsAndPositionForFrame(rect, self.bounds.origin, self.anchorPoint, &bounds, &position); + CALayer *layer = _layer; + BOOL useLayer = (layer != nil); + CGPoint origin = (useLayer ? layer.bounds.origin : self.bounds.origin); + CGPoint anchorPoint = (useLayer ? layer.anchorPoint : self.anchorPoint); - self.bounds = bounds; - self.position = position; + CGRect newBounds = CGRectZero; + CGPoint newPosition = CGPointZero; + ASBoundsAndPositionForFrame(rect, origin, anchorPoint, &newBounds, &newPosition); + + if (useLayer) { + layer.bounds = newBounds; + layer.position = newPosition; + } else { + self.bounds = newBounds; + self.position = newPosition; + } } else { // We don't have to set frame directly, but we can't read properties. // Store the frame in our pending state, and it'll get decomposed into From 5eca1e6c689c6bdbc6b73387e7ef4251c7d13d39 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 11:23:28 -0800 Subject: [PATCH 210/224] [ASDisplayNode:UIViewBridge] Clean up setNeedsDisplay/setNeedsLayout --- .../Private/ASDisplayNode+UIViewBridge.mm | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index b8dd56894f..dbbf6d504c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -37,7 +37,7 @@ #define DISPLAYNODE_USE_LOCKS 1 -#define __loaded (_view != nil || (_layer != nil && _flags.layerBacked)) +#define __loaded(node) (node->_view != nil || (node->_layer != nil && node->_flags.layerBacked)) #if DISPLAYNODE_USE_LOCKS #define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self) @@ -52,17 +52,18 @@ /// the property cannot be immediately applied and the node does not already have pending changes. /// This function must be called with the node's lock already held (after _bridge_prologue_write). ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNode *node) { + BOOL loaded = __loaded(node); if (ASDisplayNodeThreadIsMain()) { - return (node->_view != nil || (node->_layer != nil && node->_flags.layerBacked)); + return loaded; } else { - if (node.nodeLoaded && !node->_pendingViewState.hasChanges) { + if (loaded && !node->_pendingViewState.hasChanges) { [[ASPendingStateController sharedInstance] registerNode:node]; } return NO; } }; -#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded ? \ +#define _getFromViewOrLayer(layerProperty, viewAndPendingViewStateProperty) __loaded(self) ? \ (_view ? _view.viewAndPendingViewStateProperty : _layer.layerProperty )\ : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty @@ -72,9 +73,9 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo #define _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ if (shouldApply) { _view.viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } else { ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty = (viewAndPendingViewStateExpr); } -#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded ? _view.viewAndPendingViewStateProperty : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty +#define _getFromViewOnly(viewAndPendingViewStateProperty) __loaded(self) ? _view.viewAndPendingViewStateProperty : ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty -#define _getFromLayer(layerProperty) __loaded ? _layer.layerProperty : ASDisplayNodeGetPendingState(self).layerProperty +#define _getFromLayer(layerProperty) __loaded(self) ? _layer.layerProperty : ASDisplayNodeGetPendingState(self).layerProperty #define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); } @@ -238,7 +239,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo struct ASDisplayNodeFlags flags = _flags; BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked; - BOOL nodeLoaded = __loaded; + BOOL nodeLoaded = __loaded(self); BOOL isMainThread = ASDisplayNodeThreadIsMain(); if (!setFrameDirectly) { BOOL canReadProperties = isMainThread || !nodeLoaded; @@ -313,41 +314,37 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo [rasterizedContainerNode setNeedsDisplay]; }); } else { - if (self.nodeLoaded) { - if (ASDisplayNodeThreadIsMain()) { - _messageToViewOrLayer(setNeedsDisplay); - } else { - _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); - if (!pendingState.hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:self]; - } - [self __setNeedsDisplay]; - [pendingState setNeedsDisplay]; - } + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + // If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a + // message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay, + // which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared. + _messageToViewOrLayer(setNeedsDisplay); } else { - [self __setNeedsDisplay]; + [ASDisplayNodeGetPendingState(self) setNeedsDisplay]; } + [self __setNeedsDisplay]; } } - (void)setNeedsLayout { _bridge_prologue_write; - if (self.nodeLoaded) { - if (ASDisplayNodeThreadIsMain()) { - [self __setNeedsLayout]; - _messageToViewOrLayer(setNeedsLayout); - } else { - _ASPendingState *pendingState = ASDisplayNodeGetPendingState(self); - if (!pendingState.hasChanges) { - [[ASPendingStateController sharedInstance] registerNode:self]; - } - // NOTE: We will call [self __setNeedsLayout] just before we apply - // the pending state. We need to call it on main if the node is loaded - // to support implicit hierarchy management. - [pendingState setNeedsLayout]; - } + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + // The node is loaded and we're on main. + // Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging + // the view or layer to ensure that measurement and implicitly added subnodes have been handled. + [self __setNeedsLayout]; + _messageToViewOrLayer(setNeedsLayout); + } else if (__loaded(self)) { + // The node is loaded but we're not on main. + // We will call [self __setNeedsLayout] when we apply + // the pending state. We need to call it on main if the node is loaded + // to support implicit hierarchy management. + [ASDisplayNodeGetPendingState(self) setNeedsLayout]; } else { + // The node is not loaded and we're not on main. [self __setNeedsLayout]; } } @@ -531,7 +528,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (UIViewContentMode)contentMode { _bridge_prologue_read; - if (__loaded) { + if (__loaded(self)) { if (_flags.layerBacked) { return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); } else { From ff59401b05a0a1b1090929c7cb552549835b24d4 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 11:23:54 -0800 Subject: [PATCH 211/224] [ASInternalHelpers] Improve spacing --- AsyncDisplayKit/Private/ASInternalHelpers.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index de661d24b5..79f500e77c 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -51,9 +51,9 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) { - *bounds = (CGRect){ origin, rect.size }; - *position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); + *bounds = (CGRect){ origin, rect.size }; + *position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, + rect.origin.y + rect.size.height * anchorPoint.y); } @interface NSIndexPath (ASInverseComparison) From b8d2941093f1700d6e6e954042d5bfb08c4d5889 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 21 Feb 2016 11:27:36 -0800 Subject: [PATCH 212/224] [ASPendingStateController] Unite -flush and -flushNow methods, cleanup --- .../Private/ASPendingStateController.mm | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 41a11aed0a..113af7dc66 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -32,7 +32,7 @@ { self = [super init]; if (self) { - _dirtyNodes = [ASWeakSet new]; + _dirtyNodes = [[ASWeakSet alloc] init]; } return self; } @@ -40,21 +40,15 @@ + (ASPendingStateController *)sharedInstance { static dispatch_once_t onceToken; - static ASPendingStateController *controller; + static ASPendingStateController *controller = nil; dispatch_once(&onceToken, ^{ - controller = [ASPendingStateController new]; + controller = [[ASPendingStateController alloc] init]; }); return controller; } #pragma mark External API -- (void)flush -{ - ASDisplayNodeAssertMainThread(); - [self flushNow]; -} - - (void)registerNode:(ASDisplayNode *)node { ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node); @@ -64,6 +58,25 @@ [self scheduleFlushIfNeeded]; } +/** + * NOTE: There is a small re-entrancy hazard here. + * If the user gives us a subclass of UIView/CALayer that + * adds side-effects to property sets, and one side effect + * waits on a background thread that sets a view/layer property + * on a loaded node, then we've got a deadlock. + */ +- (void)flush +{ + ASDisplayNodeAssertMainThread(); + ASDN::MutexLocker l(_lock); + for (ASDisplayNode *node in _dirtyNodes) { + [node applyPendingViewState]; + } + [_dirtyNodes removeAllObjects]; + _flags.pendingFlush = NO; +} + + #pragma mark Private Methods /** @@ -77,27 +90,10 @@ _flags.pendingFlush = YES; dispatch_async(dispatch_get_main_queue(), ^{ - [self flushNow]; + [self flush]; }); } -/** - * NOTE: There is a small re-entrancy hazard here. - * If the user gives us a subclass of UIView/CALayer that - * adds side-effects to property sets, and one side effect - * waits on a background thread that sets a view/layer property - * on a loaded node, then we've got a deadlock. - */ -- (void)flushNow -{ - ASDN::MutexLocker l(_lock); - for (ASDisplayNode *node in _dirtyNodes) { - [node applyPendingViewState]; - } - [_dirtyNodes removeAllObjects]; - _flags.pendingFlush = NO; -} - @end @implementation ASPendingStateController (Testing) From 100d0a13024d369ded797b944cd3c05abc7fa961 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 08:52:26 -0800 Subject: [PATCH 213/224] [_ASPendingState] Deduplicate logic for applying setNeedsDisplay and applying frame/bounds/position --- AsyncDisplayKit/Private/_ASPendingState.mm | 83 ++++++++++------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index 2e161034bc..1946812e66 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -14,6 +14,10 @@ #import "ASInternalHelpers.h" #import "ASDisplayNodeInternal.h" +#define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \ + || (flags.setOpaque && opaque != (layer).opaque)\ + || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, (layer).backgroundColor))) + typedef struct { // Properties int needsDisplay:1; @@ -106,6 +110,30 @@ typedef struct { ASPendingStateFlags _flags; } +/** + * Apply the state's frame, bounds, and position to layer. This will not + * be called on synchronous view-backed nodes which require we directly + * call [view setFrame:]. + * + * FIXME: How should we reconcile order-of-operations between setting frame, bounds, position? + * Note we can't read bounds and position in the background, so we have to keep the frame + * value intact until application time (now). + */ +ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *state, CALayer *layer) { + ASPendingStateFlags flags = state->_flags; + if (flags.setFrame) { + CGRect _bounds; + CGPoint _position; + ASBoundsAndPositionForFrame(state->frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); + layer.bounds = _bounds; + layer.position = _position; + } else { + if (flags.setBounds) + layer.bounds = state->bounds; + if (flags.setPosition) + layer.position = state->position; + } +} @synthesize clipsToBounds=clipsToBounds; @synthesize opaque=opaque; @@ -560,9 +588,7 @@ static UIColor *defaultTintColor = nil; { ASPendingStateFlags flags = _flags; - if (flags.needsDisplay - || (flags.setOpaque && opaque != layer.opaque) - || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) { + if (__shouldSetNeedsDisplay(layer)) { [layer setNeedsDisplay]; } @@ -641,18 +667,7 @@ static UIColor *defaultTintColor = nil; if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - if (flags.setFrame) { - CGRect _bounds; - CGPoint _position; - ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); - layer.bounds = _bounds; - layer.position = _position; - } else { - if (flags.setBounds) - layer.bounds = bounds; - if (flags.setPosition) - layer.position = position; - } + ASPendingStateApplyMetricsToLayer(self, layer); } - (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly @@ -668,9 +683,7 @@ static UIColor *defaultTintColor = nil; CALayer *layer = view.layer; ASPendingStateFlags flags = _flags; - if (flags.needsDisplay - || (flags.setOpaque && opaque != view.opaque) - || (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) { + if (__shouldSetNeedsDisplay(layer)) { [view setNeedsDisplay]; } @@ -683,11 +696,6 @@ static UIColor *defaultTintColor = nil; if (flags.setZPosition) layer.zPosition = zPosition; - // This should only be used for synchronous views wrapped by nodes. - if (flags.setFrame && !(flags.setBounds && flags.setPosition)) { - view.frame = frame; - } - if (flags.setBounds) view.bounds = bounds; @@ -810,31 +818,16 @@ static UIColor *defaultTintColor = nil; if (flags.setAccessibilityIdentifier) view.accessibilityIdentifier = accessibilityIdentifier; - // FIXME: How should we reconcile order-of-operations between setting frame, bounds, position? - // Note we can't read bounds and position in the background, so we have to keep the frame - // value intact until application time (now). - if (flags.setFrame) { - if (setFrameDirectly) { - // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: - - // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform + // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: + if (flags.setFrame && setFrameDirectly) { + // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform #if DEBUG - // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); + // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. + ASDisplayNodeAssert(CATransform3DIsIdentity(layer.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif - view.frame = frame; - } else { - CGRect _bounds; - CGPoint _position; - ASBoundsAndPositionForFrame(self.frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); - layer.bounds = _bounds; - layer.position = _position; - } + view.frame = frame; } else { - if (flags.setBounds) - layer.bounds = bounds; - if (flags.setPosition) - layer.position = position; + ASPendingStateApplyMetricsToLayer(self, layer); } } From 015c024b7aeb104d210990cef1e1128412dec343 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 08:54:58 -0800 Subject: [PATCH 214/224] [ASPendingState] Give local variables default values --- AsyncDisplayKit/Private/_ASPendingState.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index 1946812e66..ddb827f28c 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -122,8 +122,8 @@ typedef struct { ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *state, CALayer *layer) { ASPendingStateFlags flags = state->_flags; if (flags.setFrame) { - CGRect _bounds; - CGPoint _position; + CGRect _bounds = CGRectZero; + CGPoint _position = CGPointZero; ASBoundsAndPositionForFrame(state->frame, layer.bounds.origin, layer.anchorPoint, &_bounds, &_position); layer.bounds = _bounds; layer.position = _position; From deccef1d94af33b6fa29f94d224f58f5c3bf2be3 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 09:04:01 -0800 Subject: [PATCH 215/224] [ASDisplayNode] Lock during constrainedSizeForCalculatedLayout --- AsyncDisplayKit/ASDisplayNode.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 4d46d80681..44c353b6c4 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1908,6 +1908,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (ASSizeRange)constrainedSizeForCalculatedLayout { + ASDN::MutexLocker l(_propertyLock); return _constrainedSize; } From 03d2b57991d2f7735db3ee52ed547f72c9191a5f Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 09:04:23 -0800 Subject: [PATCH 216/224] [ASImageNode] Unlock immediately after updating _image --- AsyncDisplayKit/ASImageNode.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 2985e0f370..fb689dc341 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -123,6 +123,7 @@ if (!ASObjectIsEqual(_image, image)) { _image = image; + ASDN::MutexUnlocker u(_imageLock); [self invalidateCalculatedLayout]; [self setNeedsDisplay]; } From f4e4c501f1db3663f0e14a467fcb4bf340771306 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 09:06:36 -0800 Subject: [PATCH 217/224] [ASDisplayNode] Remove misplaced comment that has been replaced --- AsyncDisplayKit/ASDisplayNode.mm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 44c353b6c4..74137d24f1 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1047,11 +1047,6 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes( } } -// If not rasterized (and therefore we certainly have a view or layer), -// Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded. -// Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay, -// as they don't need to display in the working range at all - since at all times onscreen, one -// -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame. - (void)__setNeedsDisplay { BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); From 57ca0c73bf3622de52cab663f713ede245cb400a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 09:12:58 -0800 Subject: [PATCH 218/224] [ASImageNode] Correctly unlock _imageLock --- AsyncDisplayKit/ASImageNode.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 2985e0f370..4239506378 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -296,7 +296,7 @@ void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; UIImage *image = _image; - ASDN::MutexLocker u(_imageLock); + ASDN::MutexUnlocker u(_imageLock); // If we've got a block to perform after displaying, do it. if (image && displayCompletionBlock) { @@ -305,7 +305,7 @@ ASDN::MutexLocker l(_imageLock); _displayCompletionBlock = nil; - ASDN::MutexLocker u(_imageLock); + ASDN::MutexUnlocker u(_imageLock); } } From 00b0968bf7dfcc14a6c6eed44ea314457f003a6b Mon Sep 17 00:00:00 2001 From: rcancro Date: Mon, 22 Feb 2016 13:00:02 -0800 Subject: [PATCH 219/224] Add ability to customize NSLayoutManager and NSTextStorage when created in the ASTextKitContext --- AsyncDisplayKit/ASTextNode.h | 10 ++++++++ AsyncDisplayKit/ASTextNode.mm | 2 ++ AsyncDisplayKit/TextKit/ASTextKitAttributes.h | 15 ++++++++--- .../TextKit/ASTextKitAttributes.mm | 3 ++- AsyncDisplayKit/TextKit/ASTextKitContext.h | 5 ++-- AsyncDisplayKit/TextKit/ASTextKitContext.mm | 12 ++++++--- .../TextKit/ASTextKitFontSizeAdjuster.mm | 4 +-- AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 5 ++-- .../TextKit/ASTextKitTailTruncater.mm | 5 ++-- .../ASTextKitTruncationTests.mm | 25 +++++++++++-------- 10 files changed, 60 insertions(+), 26 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 11dfe74e10..a1767edf63 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -213,6 +213,16 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, assign) BOOL passthroughNonlinkTouches; +#pragma mark - ASTextKit Customization +/** + A block to provide a hook to provide a custom NSLayoutManager to the ASTextKitRenderer + */ +@property (nonatomic, copy) NSLayoutManager * (^layoutManagerCreationBlock)(void); + +/** + A block to provide a hook to provide a NSTextStorage to the Text Kit's layout manager. + */ +@property (nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *attributedString); @end diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index f78d929009..3cc4e7ccbd 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -245,6 +245,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .exclusionPaths = _exclusionPaths, .pointSizeScaleFactors = _pointSizeScaleFactors, .currentScaleFactor = self.currentScaleFactor, + .layoutManagerCreationBlock = self.layoutManagerCreationBlock, + .textStorageCreationBlock = self.textStorageCreationBlock, }; } diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h index 4d2160ea33..fab9cdf7cc 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.h +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.h @@ -91,15 +91,20 @@ struct ASTextKitAttributes { */ CGFloat currentScaleFactor; /** - A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. + An optional block that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager. */ - NSLayoutManager *(*layoutManagerFactory)(void); + NSLayoutManager * (^layoutManagerCreationBlock)(void); /** An optional delegate for the NSLayoutManager */ id layoutManagerDelegate; + /** + An optional block that returns a custom NSTextStorage for the layout manager. + */ + NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *attributedString); + /** We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for the NSObjects inside. @@ -119,8 +124,9 @@ struct ASTextKitAttributes { shadowRadius, pointSizeScaleFactors, currentScaleFactor, - layoutManagerFactory, + layoutManagerCreationBlock, layoutManagerDelegate, + textStorageCreationBlock, }; }; @@ -133,7 +139,8 @@ struct ASTextKitAttributes { && shadowRadius == other.shadowRadius && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors] && currentScaleFactor == currentScaleFactor - && layoutManagerFactory == other.layoutManagerFactory + && layoutManagerCreationBlock == other.layoutManagerCreationBlock + && textStorageCreationBlock == other.textStorageCreationBlock && CGSizeEqualToSize(shadowOffset, other.shadowOffset) && _objectsEqual(exclusionPaths, other.exclusionPaths) && _objectsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet) diff --git a/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm b/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm index 5ca015fd84..e4b16f25a0 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitAttributes.mm @@ -23,7 +23,8 @@ size_t ASTextKitAttributes::hash() const [attributedString hash], [truncationAttributedString hash], [avoidTailTruncationSet hash], - std::hash()((NSUInteger) layoutManagerFactory), + std::hash()((NSUInteger) layoutManagerCreationBlock), + std::hash()((NSUInteger) textStorageCreationBlock), std::hash()(lineBreakMode), std::hash()(maximumNumberOfLines), [exclusionPaths hash], diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.h b/AsyncDisplayKit/TextKit/ASTextKitContext.h index 9fe6ab6af1..b9cd371c1e 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.h +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.h @@ -28,8 +28,9 @@ maximumNumberOfLines:(NSUInteger)maximumNumberOfLines exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize - layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory - layoutManagerDelegate:(id)layoutManagerDelegate; + layoutManagerCreationBlock:(NSLayoutManager * (^)(void))layoutCreationBlock + layoutManagerDelegate:(id)layoutManagerDelegate + textStorageCreationBlock:(NSTextStorage * (^)(NSAttributedString *attributedString))textStorageCreationBlock; @property (nonatomic, assign, readwrite) CGSize constrainedSize; diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index 42d41af217..b9925d4f7e 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -29,16 +29,22 @@ maximumNumberOfLines:(NSUInteger)maximumNumberOfLines exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize - layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory + layoutManagerCreationBlock:(NSLayoutManager * (^)(void))layoutCreationBlock layoutManagerDelegate:(id)layoutManagerDelegate + textStorageCreationBlock:(NSTextStorage * (^)(NSAttributedString *attributedString))textStorageCreationBlock + { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. static std::mutex __static_mutex; std::lock_guard l(__static_mutex); // Create the TextKit component stack with our default configuration. - _textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]); - _layoutManager = layoutManagerFactory ? layoutManagerFactory() : [[ASLayoutManager alloc] init]; + if (textStorageCreationBlock) { + _textStorage = textStorageCreationBlock(attributedString); + } else { + _textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]); + } + _layoutManager = layoutCreationBlock ? layoutCreationBlock() : [[ASLayoutManager alloc] init]; _layoutManager.usesFontLeading = NO; _layoutManager.delegate = layoutManagerDelegate; [_textStorage addLayoutManager:_layoutManager]; diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index f1be0df651..f881ae5f34 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -81,8 +81,8 @@ static std::mutex __static_mutex; std::lock_guard l(__static_mutex); - NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; - NSLayoutManager *layoutManager = _attributes.layoutManagerFactory ? _attributes.layoutManagerFactory() : [[ASLayoutManager alloc] init]; + NSTextStorage *textStorage = _attributes.textStorageCreationBlock ? _attributes.textStorageCreationBlock(attributedString) : [[NSTextStorage alloc] initWithAttributedString:attributedString]; + NSLayoutManager *layoutManager = _attributes.layoutManagerCreationBlock ? _attributes.layoutManagerCreationBlock() : [[ASLayoutManager alloc] init]; layoutManager.usesFontLeading = NO; [textStorage addLayoutManager:layoutManager]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize]; diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 825ce2e26e..5ad0e302d3 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -104,8 +104,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() maximumNumberOfLines:attributes.maximumNumberOfLines exclusionPaths:attributes.exclusionPaths constrainedSize:shadowConstrainedSize - layoutManagerFactory:attributes.layoutManagerFactory - layoutManagerDelegate:attributes.layoutManagerDelegate]; + layoutManagerCreationBlock:attributes.layoutManagerCreationBlock + layoutManagerDelegate:attributes.layoutManagerDelegate + textStorageCreationBlock:attributes.textStorageCreationBlock]; } return _context; } diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm index 0c9870b3ce..1ea45e2b91 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm @@ -72,8 +72,9 @@ maximumNumberOfLines:1 exclusionPaths:nil constrainedSize:constrainedRect.size - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; __block CGRect truncationUsedRect; [truncationContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *truncationLayoutManager, NSTextStorage *truncationTextStorage, NSTextContainer *truncationTextContainer) { diff --git a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm b/AsyncDisplayKitTests/ASTextKitTruncationTests.mm index 2bd0ba330a..d82ae0fd2d 100644 --- a/AsyncDisplayKitTests/ASTextKitTruncationTests.mm +++ b/AsyncDisplayKitTests/ASTextKitTruncationTests.mm @@ -42,8 +42,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; __block NSRange textKitVisibleRange; [context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { textKitVisibleRange = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForTextContainer:textContainer] @@ -64,8 +65,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]]; @@ -87,8 +89,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]; @@ -111,8 +114,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]; @@ -136,8 +140,9 @@ maximumNumberOfLines:0 exclusionPaths:nil constrainedSize:constrainedSize - layoutManagerFactory:nil - layoutManagerDelegate:nil]; + layoutManagerCreationBlock:nil + layoutManagerDelegate:nil + textStorageCreationBlock:nil]; XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context truncationAttributedString:[self _simpleTruncationAttributedString] From afc70b90abf053e2e8b54f5854db5504ca7e0325 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 15:10:02 -0800 Subject: [PATCH 220/224] [ASCellNode] Improve documentation, remove unused ivar --- AsyncDisplayKit/ASCellNode.h | 8 ++++---- AsyncDisplayKit/ASCellNode.m | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 5460627a0a..fccc62997b 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -79,16 +79,16 @@ typedef NSUInteger ASCellNodeAnimation; - (void)setNeedsLayout; /** - * @abstract Initializes a cell with a given viewControllerBlock. + * @abstract Initializes a cell with a given view controller block. * - * @param viewBlock The block that will be used to create the backing view. - * @param didLoadBlock The block that will be called after the view created by the viewBlock is loaded + * @param viewControllerBlock The block that will be used to create the backing view controller. + * @param didLoadBlock The block that will be called after the view controller's view is loaded. * * @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock. * The view controller's root view is resized to match the calcuated size produced during layout. * */ -- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; +- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock; - (void)visibleNodeDidScroll:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index bc980bba13..cfb329601f 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -20,7 +20,6 @@ @interface ASCellNode () { - UIViewController *_viewController; ASDisplayNode *_viewControllerNode; } From 977a509bd4e4654ecda858edb151d9dd8a52be34 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 15:11:27 -0800 Subject: [PATCH 221/224] [ASCellNode] If viewControllerBlock returns an ASViewController, use its node directly instead of wrapping --- AsyncDisplayKit/ASCellNode.m | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index cfb329601f..5083edf12c 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -13,6 +13,7 @@ #import #import +#import #import #pragma mark - @@ -20,6 +21,8 @@ @interface ASCellNode () { + ASDisplayNodeViewControllerBlock _viewControllerBlock; + ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; ASDisplayNode *_viewControllerNode; } @@ -45,20 +48,41 @@ return nil; ASDisplayNodeAssertNotNil(viewControllerBlock, @"should initialize with a valid block that returns a UIViewController"); - - if (viewControllerBlock) { - - _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - _viewController = viewControllerBlock(); - return _viewController.view; - } didLoadBlock:didLoadBlock]; - - [self addSubnode:_viewControllerNode]; - } + _viewControllerBlock = viewControllerBlock; + _viewControllerDidLoadBlock = didLoadBlock; return self; } +- (void)didLoad +{ + [super didLoad]; + + if (_viewControllerBlock != nil) { + + UIViewController *viewController = _viewControllerBlock(); + _viewControllerBlock = nil; + + if ([viewController isKindOfClass:[ASViewController class]]) { + ASViewController *asViewController = (ASViewController *)viewController; + _viewControllerNode = asViewController.node; + } else { + _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ + return viewController.view; + }]; + } + [self addSubnode:_viewControllerNode]; + + // Since we just loaded our node, and added _viewControllerNode as a subnode, + // _viewControllerNode must have just loaded its view, so now is an appropriate + // time to execute our didLoadBlock, if we were given one. + if (_viewControllerDidLoadBlock != nil) { + _viewControllerDidLoadBlock(self); + _viewControllerDidLoadBlock = nil; + } + } +} + - (void)layout { [super layout]; From 767bff2e1ae0bd6a1ff6160c297759ff423a29ba Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 15:12:13 -0800 Subject: [PATCH 222/224] [ASInternalHelpers] Use ASDisplayNodeThreadIsMain, reduce block allocations --- AsyncDisplayKit/ASDisplayNode.mm | 8 ++------ AsyncDisplayKit/Private/ASInternalHelpers.mm | 15 ++++++--------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 906cfc43fa..1397b33ced 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1238,13 +1238,9 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD // Otherwise there is no way for the subnode's view or layer to enter the hierarchy, except recursing down all // subnodes on the main thread after the node tree has been created but before the first display (which // could introduce performance problems). - if (ASDisplayNodeThreadIsMain()) { + ASPerformBlockOnMainThread(^{ [self _addSubnodeSubviewOrSublayer:subnode]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self _addSubnodeSubviewOrSublayer:subnode]; - }); - } + }); } ASDisplayNodeAssert(isMovingEquivalentParents == disableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 51e8d2685b..0f6aa6f08d 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -13,6 +13,7 @@ #import #import +#import "ASThread.h" #import "ASLayout.h" BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) @@ -35,7 +36,7 @@ BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL sele static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block) { - if ([NSThread isMainThread]) { + if (ASDisplayNodeThreadIsMain()) { dispatch_once(predicate, block); } else { if (DISPATCH_EXPECT(*predicate == 0L, NO)) { @@ -48,21 +49,17 @@ static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_bloc void ASPerformBlockOnMainThread(void (^block)()) { - if ([NSThread isMainThread]) { + if (ASDisplayNodeThreadIsMain()) { block(); } else { - dispatch_async(dispatch_get_main_queue(), ^{ - block(); - }); + dispatch_async(dispatch_get_main_queue(), block); } } void ASPerformBlockOnBackgroundThread(void (^block)()) { - if ([NSThread isMainThread]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - block(); - }); + if (ASDisplayNodeThreadIsMain()) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); } else { block(); } From fd5723379f7aea00db318d8859da02d55cbf2997 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Feb 2016 18:48:31 -0800 Subject: [PATCH 223/224] [ASImageNode] Lock & unlock directly instead of using stack objects. --- AsyncDisplayKit/ASImageNode.mm | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 4239506378..18269649e1 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -291,21 +291,19 @@ { [super displayDidFinish]; - ASDN::MutexLocker l(_imageLock); - - void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; - UIImage *image = _image; - - ASDN::MutexUnlocker u(_imageLock); + _imageLock.lock(); + void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; + UIImage *image = _image; + _imageLock.unlock(); // If we've got a block to perform after displaying, do it. if (image && displayCompletionBlock) { displayCompletionBlock(NO); - - ASDN::MutexLocker l(_imageLock); - _displayCompletionBlock = nil; - ASDN::MutexUnlocker u(_imageLock); + + _imageLock.lock(); + _displayCompletionBlock = nil; + _imageLock.unlock(); } } From 359d19da2992187457f4daae4de93443030b8c4b Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 22 Feb 2016 21:58:45 -0800 Subject: [PATCH 224/224] ASNetworkImageNode should support a nil cache. --- AsyncDisplayKit/ASNetworkImageNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 55263c1051..5071c87193 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -64,7 +64,7 @@ _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; - ASDisplayNodeAssert([cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); + ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];