From 5897c1e1b47a805169aaaacb70d50283efa13d34 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Sat, 16 Apr 2016 13:19:07 +0100 Subject: [PATCH 01/17] [ASMapNode] Remove false assert. This handles #1534 --- AsyncDisplayKit/ASMapNode.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index c44b0b0c78..2454a13bca 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -209,7 +209,6 @@ - (void)setUpSnapshotter { - ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar."); _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; } From 8d20321d67ef0c2b4e01b248b3852063d7a5213b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 15 Apr 2016 10:50:10 -0700 Subject: [PATCH 02/17] Fix ASTableNode / ASCollectionNode backgroundColor does not work If the background color is applied via the pending state it's applied to the layer of the UICollectionView / UITableview. Unfortunately UITableView / UICollectionView does not consider using the layer backgroundColor property as it's background color, so it needs to be applied to the view after the ASCollectionNode / ASTableNode did load and the view is available --- AsyncDisplayKit/ASCollectionNode.mm | 24 ++++++++++++++++++++++++ AsyncDisplayKit/ASTableNode.m | 27 ++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index d33ef7bb0d..605f0defaa 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -16,6 +16,11 @@ @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; + +// If the background color is applied via the pending state it's applied to the layer of the UICollectionView. +// Unfortunately UICollectionView does not consider using the layer backgroundColor property as it's background color, +// so it needs to be applied to the view after the ASCollectionNode did load and the view is available +@property (strong, nonatomic) UIColor *backgroundColor; @end @implementation _ASCollectionPendingState @@ -162,6 +167,25 @@ } } +- (void)setBackgroundColor:(UIColor *)backgroundColor +{ + if ([self pendingState]) { + _pendingState.backgroundColor = backgroundColor; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.backgroundColor = backgroundColor; + } +} + +- (UIColor *)backgroundColor +{ + if ([self pendingState]) { + return _pendingState.backgroundColor; + } else { + return self.view.backgroundColor; + } +} + - (ASCollectionView *)view { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 366209bf15..9906acb5c4 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -14,6 +14,11 @@ @interface _ASTablePendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; + +// If the background color is applied via the pending state it's applied to the layer of the UITableView. +// Unfortunately UITableView does not consider using the layer backgroundColor property as it's background color, +// so it needs to be applied to the view after the ASTableNode did load and the view is available +@property (strong, nonatomic) UIColor *backgroundColor; @end @implementation _ASTablePendingState @@ -68,12 +73,13 @@ ASTableView *view = self.view; view.tableNode = self; - + if (_pendingState) { _ASTablePendingState *pendingState = _pendingState; self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; + view.backgroundColor = pendingState.backgroundColor; } } @@ -133,6 +139,25 @@ } } +- (void)setBackgroundColor:(UIColor *)backgroundColor +{ + if ([self pendingState]) { + _pendingState.backgroundColor = backgroundColor; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.backgroundColor = backgroundColor; + } +} + +- (UIColor *)backgroundColor +{ + if ([self pendingState]) { + return _pendingState.backgroundColor; + } else { + return self.view.backgroundColor; + } +} + - (ASTableView *)view { return (ASTableView *)[super view]; From e65d63a5024bf2cdbcfedc012f2a3e51c395fbf0 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 16:20:47 -0700 Subject: [PATCH 03/17] Simplified and improved video node's handling of state changes: - Simplified visibility state change handling. - Placeholder image is now updated after switching assets. - Improves memory usage by clearing the placeholder image in clearFetchedData. --- AsyncDisplayKit/ASVideoNode.mm | 317 +++++++++++------------- AsyncDisplayKitTests/ASVideoNodeTests.m | 27 ++ 2 files changed, 174 insertions(+), 170 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 1a345b5f3c..b4cd2e9dca 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -26,6 +26,9 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } } +static void *ASVideoNodeContext = &ASVideoNodeContext; +static NSString * const kStatus = @"status"; + @interface ASVideoNode () { ASDN::RecursiveMutex _videoLock; @@ -81,72 +84,62 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (ASDisplayNode *)constructPlayerNode { - ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - ASDN::MutexLocker l(_videoLock); - + ASVideoNode * __weak weakSelf = self; + + return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - playerLayer.player = _player; - playerLayer.videoGravity = [self gravity]; + playerLayer.player = weakSelf.player; + playerLayer.videoGravity = weakSelf.gravity; return playerLayer; }]; - - return playerNode; } -- (void)constructCurrentPlayerItemFromInitData +- (AVPlayerItem *)constructPlayerItem { ASDN::MutexLocker l(_videoLock); ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); - [self removePlayerItemObservers]; + + AVPlayerItem *playerItem = nil; - if (_asset) { - if ([_asset.tracks count]) { - _currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } else { - _currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; + if (_asset != nil) { + if (_asset.tracks.count > 0) { + playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; + } else if ([_asset isKindOfClass:[AVURLAsset class]]) { + playerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; } } - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - } + + return playerItem; } -- (void)removePlayerItemObservers +- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem { - ASDN::MutexLocker l(_videoLock); - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - } + [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; + [notificationCenter addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; } -- (void)didLoad +- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem { - [super didLoad]; - - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - _playerNode = [self constructPlayerNode]; - [self insertSubnode:_playerNode atIndex:0]; - } else if (_asset) { - [self setPlaceholderImagefromAsset:_asset]; + @try { + [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; } + @catch (NSException * __unused exception) { + NSLog(@"Unnecessary KVO removal"); + } + + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; + [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; + [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; + [notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)layout @@ -169,25 +162,43 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); } -- (void)setPlaceholderImagefromAsset:(AVAsset*)asset +- (void)generatePlaceholderImage +{ + ASVideoNode * __weak weakSelf = self; + AVAsset * __weak asset = self.asset; + + [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) { + ASPerformBlockOnMainThread(^{ + // Ensure the asset hasn't changed since the image request was made + if (ASAssetIsEqual(weakSelf.asset, asset)) { + [weakSelf setPlaceholderImage:image]; + } + }); + }]; +} + +- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler { ASPerformBlockOnBackgroundThread(^{ ASDN::MutexLocker l(_videoLock); - - AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; - imageGenerator.appliesPreferredTrackTransform = YES; - NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; - - [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { - - ASDN::MutexLocker l(_videoLock); - - // Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here - // http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error - if (image && _placeholderImageNode.image == nil) { - [self setPlaceholderImage:[UIImage imageWithCGImage:image]]; - } - }]; + + // Skip the asset image generation if we don't have any tracks available that are capable of supporting it + NSArray* visualAssetArray = [_asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]; + if (visualAssetArray.count == 0) { + completionHandler(nil); + return; + } + + AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset]; + previewImageGenerator.appliesPreferredTrackTransform = YES; + + [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] + completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { + if (error != nil && result != AVAssetImageGeneratorCancelled) { + NSLog(@"Asset preview image generation failed with error: %@", error); + } + completionHandler(image ? [UIImage imageWithCGImage:image] : nil); + }]; }); } @@ -195,7 +206,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); - if (_placeholderImageNode == nil) { + if (_placeholderImageNode == nil && image != nil) { _placeholderImageNode = [[ASImageNode alloc] init]; _placeholderImageNode.layerBacked = YES; _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); @@ -206,61 +217,30 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { dispatch_async(dispatch_get_main_queue(), ^{ ASDN::MutexLocker l(_videoLock); - [self insertSubnode:_placeholderImageNode atIndex:0]; - [self setNeedsLayout]; + if (_placeholderImageNode != nil) { + [self insertSubnode:_placeholderImageNode atIndex:0]; + [self setNeedsLayout]; + } }); } -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - ASDN::MutexLocker l(_videoLock); - - if (!nowVisible) { - if (wasVisible) { - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } - [(UIActivityIndicatorView *)_spinner.view stopAnimating]; - [_spinner removeFromSupernode]; - } - } else { - if (_shouldBePlaying) { - [self play]; - } - } -} - - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { ASDN::MutexLocker l(_videoLock); - - if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { - if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { - if ([self.subnodes containsObject:_spinner]) { - [_spinner removeFromSupernode]; - _spinner = nil; - } - + + if (object != _currentPlayerItem) { + return; + } + + if ([keyPath isEqualToString:kStatus]) { + if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + [_spinner removeFromSupernode]; + _spinner = nil; + // If we don't yet have a placeholder image update it now that we should have data available for it if (_placeholderImageNode.image == nil) { - if (_currentPlayerItem && - _currentPlayerItem.tracks.count > 0 && - _currentPlayerItem.tracks[0].assetTrack && - _currentPlayerItem.tracks[0].assetTrack.asset) { - _asset = _currentPlayerItem.tracks[0].assetTrack.asset; - [self setPlaceholderImagefromAsset:_asset]; - [self setNeedsLayout]; - } + [self generatePlaceholderImage]; } - - } else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { - } } } @@ -282,23 +262,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { [super fetchData]; - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in fetch data"); - } - { ASDN::MutexLocker l(_videoLock); - [self constructCurrentPlayerItemFromInitData]; - [_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; - - if (_player) { - [_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; - } else { - _player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; + + AVPlayerItem *playerItem = [self constructPlayerItem]; + self.currentItem = playerItem; + self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; + + if (_placeholderImageNode.image == nil) { + [self generatePlaceholderImage]; } } } @@ -309,8 +281,10 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); - ((AVPlayerLayer *)_playerNode.layer).player = nil; - _player = nil; + + self.player = nil; + self.currentItem = nil; + _placeholderImageNode.image = nil; } } @@ -320,24 +294,13 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { ASDN::MutexLocker l(_videoLock); - if (_shouldAutoplay && _playerNode.isNodeLoaded) { - [self play]; - } else if (_shouldAutoplay) { - _shouldBePlaying = YES; - } if (isVisible) { - if (_playerNode.isNodeLoaded) { - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - ((AVPlayerLayer *)_playerNode.layer).player = _player; - } - - if (_shouldBePlaying) { + if (_shouldBePlaying || _shouldAutoplay) { [self play]; } + } else if (_shouldBePlaying) { + [self pause]; + _shouldBePlaying = YES; } } @@ -373,11 +336,14 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { return; } + [self clearFetchedData]; + _asset = asset; - - // FIXME: Adopt -setNeedsFetchData when it is available - if (self.interfaceState & ASInterfaceStateFetchData) { - [self fetchData]; + + [self setNeedsDataFetch]; + + if (_shouldAutoplay) { + [self play]; } } @@ -430,20 +396,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (void)play { ASDN::MutexLocker l(_videoLock); - - if (!_spinner) { - _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.color = [UIColor whiteColor]; - - return spinnnerView; - }]; + + if (_player == nil) { + [self setNeedsDataFetch]; } - - if (!_playerNode) { + + if (_playerNode == nil) { _playerNode = [self constructPlayerNode]; - - if ([self.subnodes containsObject:_playButton]) { + + if (_playButton.supernode == self) { [self insertSubnode:_playerNode belowSubnode:_playButton]; } else { [self addSubnode:_playerNode]; @@ -456,9 +417,19 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { [UIView animateWithDuration:0.15 animations:^{ _playButton.alpha = 0.0; }]; - - if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [self addSubnode:_spinner]; + + if (![self ready]) { + if (!_spinner) { + _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; + spinnnerView.color = [UIColor whiteColor]; + + return spinnnerView; + }]; + + [self addSubnode:_spinner]; + } + [(UIActivityIndicatorView *)_spinner.view startAnimating]; } } @@ -540,7 +511,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } } -#pragma mark - Property Accessors for Tests +#pragma mark - Internal Properties - (ASDisplayNode *)spinner { @@ -563,7 +534,12 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - (void)setCurrentItem:(AVPlayerItem *)currentItem { ASDN::MutexLocker l(_videoLock); + + [self removePlayerItemObservers:_currentPlayerItem]; + _currentPlayerItem = currentItem; + + [self addPlayerItemObservers:currentItem]; } - (ASDisplayNode *)playerNode @@ -582,6 +558,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { { ASDN::MutexLocker l(_videoLock); _player = player; + player.muted = _muted; + ((AVPlayerLayer *)_playerNode.layer).player = player; } - (BOOL)shouldBePlaying @@ -590,19 +568,18 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { return _shouldBePlaying; } +- (void)setShouldBePlaying:(BOOL)shouldBePlaying +{ + ASDN::MutexLocker l(_videoLock); + _shouldBePlaying = shouldBePlaying; +} + #pragma mark - Lifecycle - (void)dealloc { [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - [self removePlayerItemObservers]; - - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in dealloc"); - } + [self removePlayerItemObservers:_currentPlayerItem]; } @end diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index f9f3acd9d9..81224550fa 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -362,4 +362,31 @@ XCTAssertNotEqual(_videoNode, firstButton.supernode); } +- (void)testChangingAssetsChangesPlaceholderImage +{ + UIImage *firstImage = [[UIImage alloc] init]; + + _videoNode.asset = _firstAsset; + [_videoNode setPlaceholderImage:firstImage]; + XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image); + + _videoNode.asset = _secondAsset; + XCTAssertNotEqual(firstImage, _videoNode.placeholderImageNode.image); +} + +- (void)testClearingFetchedContentShouldClearAssetData +{ + _videoNode.asset = _firstAsset; + [_videoNode fetchData]; + [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + XCTAssertNotNil(_videoNode.player); + XCTAssertNotNil(_videoNode.currentItem); + XCTAssertNotNil(_videoNode.placeholderImageNode.image); + + [_videoNode clearFetchedData]; + XCTAssertNil(_videoNode.player); + XCTAssertNil(_videoNode.currentItem); + XCTAssertNil(_videoNode.placeholderImageNode.image); +} + @end From b5c3b15069ce9cfbd426d6e5dc758687cf3f6310 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 16:22:27 -0700 Subject: [PATCH 04/17] Observe playbackLikelyToKeepUp in order to handle player buffer filling and emptying --- AsyncDisplayKit/ASVideoNode.mm | 30 ++++++------------------- AsyncDisplayKitTests/ASVideoNodeTests.m | 14 +++++------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index b4cd2e9dca..5a5895f303 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -27,6 +27,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { } static void *ASVideoNodeContext = &ASVideoNodeContext; +static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kStatus = @"status"; @interface ASVideoNode () @@ -116,19 +117,19 @@ static NSString * const kStatus = @"status"; - (void)addPlayerItemObservers:(AVPlayerItem *)playerItem { [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; + [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)removePlayerItemObservers:(AVPlayerItem *)playerItem { @try { [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; + [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext]; } @catch (NSException * __unused exception) { NSLog(@"Unnecessary KVO removal"); @@ -138,8 +139,6 @@ static NSString * const kStatus = @"status"; [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; - [notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)layout @@ -242,6 +241,10 @@ static NSString * const kStatus = @"status"; [self generatePlaceholderImage]; } } + } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true) { + [self play]; // autoresume after buffer catches up + } } } @@ -492,25 +495,6 @@ static NSString * const kStatus = @"status"; } } -- (void)willEnterForeground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self play]; - } -} - -- (void)didEnterBackground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } -} - #pragma mark - Internal Properties - (ASDisplayNode *)spinner diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 81224550fa..4d6b471c9a 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -29,7 +29,7 @@ @property (atomic, readonly) ASImageNode *placeholderImageNode; @property (atomic, readwrite) ASDisplayNode *playerNode; @property (atomic, readwrite) AVPlayer *player; -@property (atomic, readonly) BOOL shouldBePlaying; +@property (atomic, readwrite) BOOL shouldBePlaying; - (void)setPlaceholderImage:(UIImage *)image; @@ -313,21 +313,17 @@ XCTAssertTrue(_videoNode.isPlaying); } -- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume +- (void)testVideoResumedWhenBufferIsLikelyToKeepUp { _videoNode.asset = _firstAsset; - [_videoNode didLoad]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; - [_videoNode play]; - - XCTAssertTrue(_videoNode.isPlaying); - - [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + [_videoNode pause]; + _videoNode.shouldBePlaying = YES; XCTAssertFalse(_videoNode.isPlaying); - [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL]; XCTAssertTrue(_videoNode.isPlaying); } From a2b03d6e902fea8e5b0ac18150b14f5a1c8e243e Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 22:06:03 -0700 Subject: [PATCH 05/17] Change ASContentModeFromVideoGravity's default to UIViewContentModeScaleAspectFit --- AsyncDisplayKit/ASVideoNode.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 5a5895f303..c1fecba3c7 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -17,12 +17,12 @@ static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { } static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { - return UIViewContentModeScaleAspectFit; - } else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { + if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { return UIViewContentModeScaleAspectFill; - } else { + } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) { return UIViewContentModeScaleToFill; + } else { + return UIViewContentModeScaleAspectFit; } } From 6a4bc3fd5b808bb670627ac1afcf5d63ff4baa79 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 22:30:32 -0700 Subject: [PATCH 06/17] Rename setPlaceholderImage to setVideoPlaceholderImage in order to prevent super class setter conflict --- AsyncDisplayKit/ASVideoNode.mm | 4 ++-- AsyncDisplayKitTests/ASVideoNodeTests.m | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index c1fecba3c7..63b00830bc 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -170,7 +170,7 @@ static NSString * const kStatus = @"status"; ASPerformBlockOnMainThread(^{ // Ensure the asset hasn't changed since the image request was made if (ASAssetIsEqual(weakSelf.asset, asset)) { - [weakSelf setPlaceholderImage:image]; + [weakSelf setVideoPlaceholderImage:image]; } }); }]; @@ -201,7 +201,7 @@ static NSString * const kStatus = @"status"; }); } -- (void)setPlaceholderImage:(UIImage *)image +- (void)setVideoPlaceholderImage:(UIImage *)image { ASDN::MutexLocker l(_videoLock); diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 4d6b471c9a..3effa2431a 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -31,7 +31,7 @@ @property (atomic, readwrite) AVPlayer *player; @property (atomic, readwrite) BOOL shouldBePlaying; -- (void)setPlaceholderImage:(UIImage *)image; +- (void)setVideoPlaceholderImage:(UIImage *)image; @end @@ -330,7 +330,7 @@ - (void)testSettingVideoGravityChangesPlaceholderContentMode { - [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); _videoNode.gravity = AVLayerVideoGravityResize; @@ -363,7 +363,7 @@ UIImage *firstImage = [[UIImage alloc] init]; _videoNode.asset = _firstAsset; - [_videoNode setPlaceholderImage:firstImage]; + [_videoNode setVideoPlaceholderImage:firstImage]; XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image); _videoNode.asset = _secondAsset; @@ -374,7 +374,7 @@ { _videoNode.asset = _firstAsset; [_videoNode fetchData]; - [_videoNode setPlaceholderImage:[[UIImage alloc] init]]; + [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; XCTAssertNotNil(_videoNode.player); XCTAssertNotNil(_videoNode.currentItem); XCTAssertNotNil(_videoNode.placeholderImageNode.image); From a49d620771d039a165e66937e4723d79223431af Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Mon, 18 Apr 2016 23:08:31 -0700 Subject: [PATCH 07/17] Replace dispatch_async(dispatch_get_main_queue... with ASPerformBlockOnMainThread --- AsyncDisplayKit/ASVideoNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 63b00830bc..5170e2e19f 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -213,7 +213,7 @@ static NSString * const kStatus = @"status"; _placeholderImageNode.image = image; - dispatch_async(dispatch_get_main_queue(), ^{ + ASPerformBlockOnMainThread(^{ ASDN::MutexLocker l(_videoLock); if (_placeholderImageNode != nil) { From ccb5860c78b0f6bc4cfda661b54fce35fc9779c2 Mon Sep 17 00:00:00 2001 From: Vivian Qu Date: Tue, 19 Apr 2016 10:46:55 -0700 Subject: [PATCH 08/17] Update ASSentinel to use OSAtomicAdd32 to support iOS7.0 --- AsyncDisplayKit/Private/ASSentinel.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASSentinel.m b/AsyncDisplayKit/Private/ASSentinel.m index bf9ee4799e..ed252380a5 100644 --- a/AsyncDisplayKit/Private/ASSentinel.m +++ b/AsyncDisplayKit/Private/ASSentinel.m @@ -22,7 +22,7 @@ - (int32_t)increment { - return OSAtomicIncrement32(&_value); + return OSAtomicAdd32(1, &_value); } @end From 3793dc024e39590f815073153ace615c275a1e3d Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 19 Apr 2016 10:50:22 -0700 Subject: [PATCH 09/17] Improve setting special properties for certain classes directly to the UIView - Remove duplicated code in ASCollectionNode and ASTableNode - Fix setting the pending state to the view if applying the pending state to the view --- AsyncDisplayKit/ASCollectionNode.mm | 24 ----------------- AsyncDisplayKit/ASDisplayNode.mm | 11 ++++++-- AsyncDisplayKit/ASTableNode.m | 27 +------------------ .../Private/ASDisplayNode+UIViewBridge.mm | 11 ++++++-- .../Private/ASDisplayNodeInternal.h | 2 ++ AsyncDisplayKit/Private/_ASPendingState.h | 2 +- AsyncDisplayKit/Private/_ASPendingState.mm | 14 +++++++--- 7 files changed, 32 insertions(+), 59 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 605f0defaa..d33ef7bb0d 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -16,11 +16,6 @@ @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; - -// If the background color is applied via the pending state it's applied to the layer of the UICollectionView. -// Unfortunately UICollectionView does not consider using the layer backgroundColor property as it's background color, -// so it needs to be applied to the view after the ASCollectionNode did load and the view is available -@property (strong, nonatomic) UIColor *backgroundColor; @end @implementation _ASCollectionPendingState @@ -167,25 +162,6 @@ } } -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - if ([self pendingState]) { - _pendingState.backgroundColor = backgroundColor; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.backgroundColor = backgroundColor; - } -} - -- (UIColor *)backgroundColor -{ - if ([self pendingState]) { - return _pendingState.backgroundColor; - } else { - return self.view.backgroundColor; - } -} - - (ASCollectionView *)view { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 56f7f85458..7ad4ee3b6c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -83,6 +83,13 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } +// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties +// like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only. +BOOL ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(ASDisplayNodeFlags flags) +{ + return flags.synchronous && !flags.layerBacked; +} + _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) { ASDN::MutexLocker l(node->_propertyLock); @@ -954,8 +961,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (self.layerBacked) { [_pendingViewState applyToLayer:self.layer]; } else { - BOOL setFrameDirectly = (_flags.synchronous && !_flags.layerBacked); - [_pendingViewState applyToView:self.view setFrameDirectly:setFrameDirectly]; + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(_flags); + [_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling]; } [_pendingViewState clearChanges]; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 9906acb5c4..22b6c3926f 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -14,18 +14,13 @@ @interface _ASTablePendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; - -// If the background color is applied via the pending state it's applied to the layer of the UITableView. -// Unfortunately UITableView does not consider using the layer backgroundColor property as it's background color, -// so it needs to be applied to the view after the ASTableNode did load and the view is available -@property (strong, nonatomic) UIColor *backgroundColor; @end @implementation _ASTablePendingState @end @interface ASTableNode () -@property (nonatomic) _ASTablePendingState *pendingState; +@property (nonatomic, strong) _ASTablePendingState *pendingState; @end @interface ASTableView () @@ -79,7 +74,6 @@ self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; - view.backgroundColor = pendingState.backgroundColor; } } @@ -139,25 +133,6 @@ } } -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - if ([self pendingState]) { - _pendingState.backgroundColor = backgroundColor; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.backgroundColor = backgroundColor; - } -} - -- (UIColor *)backgroundColor -{ - if ([self pendingState]) { - return _pendingState.backgroundColor; - } else { - return self.view.backgroundColor; - } -} - - (ASTableView *)view { return (ASTableView *)[super view]; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index eaf0cc9f59..c777857485 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -19,6 +19,7 @@ #import "ASPendingStateController.h" #import "ASThread.h" #import "ASTextNode.h" +#import "ASTableNode.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -239,11 +240,11 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: struct ASDisplayNodeFlags flags = _flags; - BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked; + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(flags); BOOL nodeLoaded = __loaded(self); BOOL isMainThread = ASDisplayNodeThreadIsMain(); - if (!setFrameDirectly) { + if (!specialPropertiesHandling) { BOOL canReadProperties = isMainThread || !nodeLoaded; if (canReadProperties) { // We don't have to set frame directly, and we can read current properties. @@ -583,6 +584,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo if (shouldApply) { CGColorRef oldBackgroundCGColor = _layer.backgroundColor; _layer.backgroundColor = newBackgroundCGColor; + + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(_flags); + if (specialPropertiesHandling) { + _view.backgroundColor = newBackgroundColor; + } + if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) { [self setNeedsDisplay]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index ae3f3c477a..ae7616add2 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -25,8 +25,10 @@ @class _ASDisplayLayer; @class _ASPendingState; @class ASSentinel; +struct ASDisplayNodeFlags; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); +BOOL ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(ASDisplayNodeFlags flags); /// Get the pending view state for the node, creating one if needed. _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node); diff --git a/AsyncDisplayKit/Private/_ASPendingState.h b/AsyncDisplayKit/Private/_ASPendingState.h index 8f6702465f..40986b18af 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 setFrameDirectly:(BOOL)setFrameDirectly; +- (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)setFrameDirectly; - (void)applyToLayer:(CALayer *)layer; + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index b85cbb57c3..303bf2a58c 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -745,7 +745,7 @@ static UIColor *defaultTintColor = nil; ASPendingStateApplyMetricsToLayer(self, layer); } -- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly +- (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPropertiesHandling { /* Use our convenience setters blah here instead of layer.blah @@ -789,9 +789,16 @@ static UIColor *defaultTintColor = nil; if (flags.setClipsToBounds) view.clipsToBounds = clipsToBounds; - if (flags.setBackgroundColor) + if (flags.setBackgroundColor) { + // Set the background color to the layer as in the UIView bridge we use this value as background color layer.backgroundColor = backgroundColor; + // We have to make sure certain nodes get the background color call directly + if (specialPropertiesHandling) { + view.backgroundColor = [UIColor colorWithCGColor:backgroundColor]; + } + } + if (flags.setTintColor) view.tintColor = self.tintColor; @@ -907,8 +914,7 @@ static UIColor *defaultTintColor = nil; if (flags.setAccessibilityPath) view.accessibilityPath = accessibilityPath; - // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: - if (flags.setFrame && setFrameDirectly) { + if (flags.setFrame && specialPropertiesHandling) { // 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. From f01ceab3e721ca18ab7133efa93fa515d9996b91 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Tue, 19 Apr 2016 13:04:35 -0700 Subject: [PATCH 10/17] Reuse the existing AVPlayer when it's available in fetchData --- AsyncDisplayKit/ASVideoNode.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 5170e2e19f..fc3e8e13ad 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -270,7 +270,12 @@ static NSString * const kStatus = @"status"; AVPlayerItem *playerItem = [self constructPlayerItem]; self.currentItem = playerItem; - self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; + + if (_player != nil) { + [_player replaceCurrentItemWithPlayerItem:playerItem]; + } else { + self.player = [AVPlayer playerWithPlayerItem:playerItem]; + } if (_placeholderImageNode.image == nil) { [self generatePlaceholderImage]; From 4a010337f69e077a3d0a67f7de99a44f25d6cef5 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Tue, 19 Apr 2016 13:21:06 -0700 Subject: [PATCH 11/17] Limit resuming of buffering videos to those that are visible --- AsyncDisplayKit/ASVideoNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 5170e2e19f..297f3ced59 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -242,7 +242,7 @@ static NSString * const kStatus = @"status"; } } } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true) { + if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { [self play]; // autoresume after buffer catches up } } From 0fc39d1ccd3d650512e935b8447570f16ed804c7 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 19 Apr 2016 13:13:06 -0700 Subject: [PATCH 12/17] Follow up to animated GIF PR --- AsyncDisplayKit.xcodeproj/project.pbxproj | 6 --- AsyncDisplayKit/ASImageNode+AnimatedImage.h | 15 ------- AsyncDisplayKit/ASImageNode+AnimatedImage.mm | 27 ++++------- AsyncDisplayKit/ASImageNode.h | 18 ++++++++ AsyncDisplayKit/ASImageNode.mm | 1 - AsyncDisplayKit/ASNetworkImageNode.mm | 1 - AsyncDisplayKit/AsyncDisplayKit.h | 1 - AsyncDisplayKit/Details/ASImageProtocols.h | 41 +++++++++++++++++ .../ASImageNode+AnimatedImagePrivate.h | 1 - .../ASAnimatedImage/AppDelegate.m | 45 ------------------- .../ASAnimatedImage/AppDelegate.h | 0 .../AnimatedGIF/ASAnimatedImage/AppDelegate.m | 23 ++++++++++ .../AppIcon.appiconset/Contents.json | 0 .../Base.lproj/LaunchScreen.storyboard | 0 .../Base.lproj/Main.storyboard | 0 .../ASAnimatedImage/Info.plist | 0 .../ASAnimatedImage/ViewController.h | 0 .../ASAnimatedImage/ViewController.m | 5 --- .../ASAnimatedImage/main.m | 0 .../{ASAnimatedImage => AnimatedGIF}/Podfile | 0 .../Sample.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/xcschemes/Sample.xcscheme | 0 .../contents.xcworkspacedata | 0 24 files changed, 91 insertions(+), 93 deletions(-) delete mode 100644 AsyncDisplayKit/ASImageNode+AnimatedImage.h delete mode 100644 examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/AppDelegate.h (100%) create mode 100644 examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/Base.lproj/Main.storyboard (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/Info.plist (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/ViewController.h (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/ViewController.m (87%) rename examples/{ASAnimatedImage => AnimatedGIF}/ASAnimatedImage/main.m (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/Podfile (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/Sample.xcodeproj/project.pbxproj (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme (100%) rename examples/{ASAnimatedImage => AnimatedGIF}/Sample.xcworkspace/contents.xcworkspacedata (100%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 5c12f1cac3..cd8d5a071c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -251,9 +251,7 @@ 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; - 68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; - 68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -759,7 +757,6 @@ 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 = ""; }; - 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+AnimatedImage.h"; sourceTree = ""; }; 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = ""; }; 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = ""; }; @@ -1048,7 +1045,6 @@ 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */, 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, - 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, @@ -1536,7 +1532,6 @@ 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, - 68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, @@ -1649,7 +1644,6 @@ 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, - 68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */, diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.h b/AsyncDisplayKit/ASImageNode+AnimatedImage.h deleted file mode 100644 index ae6ee4e7c0..0000000000 --- a/AsyncDisplayKit/ASImageNode+AnimatedImage.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASImageNode+AnimatedImage.h -// AsyncDisplayKit -// -// Created by Garrett Moon on 3/22/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import "ASImageNode.h" -#import "ASImageProtocols.h" - -@interface ASImageNode (AnimatedImage) -@property (atomic, assign) BOOL animatedImagePaused; -@property (nullable, atomic, strong) id animatedImage; -@end diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm index ae296ab608..ffebb96317 100644 --- a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm +++ b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm @@ -6,7 +6,7 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import "ASImageNode+AnimatedImage.h" +#import "ASImageNode.h" #import "ASAssert.h" #import "ASImageProtocols.h" @@ -25,9 +25,12 @@ - (void)setAnimatedImage:(id )animatedImage { ASDN::MutexLocker l(_animatedImageLock); - if (!ASObjectIsEqual(_animatedImage, animatedImage)) { - _animatedImage = animatedImage; + if (ASObjectIsEqual(_animatedImage, animatedImage)) { + return; } + + _animatedImage = animatedImage; + if (animatedImage != nil) { __weak ASImageNode *weakSelf = self; if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { @@ -50,7 +53,7 @@ - (void)setAnimatedImagePaused:(BOOL)animatedImagePaused { - ASDN::MutexLocker l(_animatedImagePausedLock); + ASDN::MutexLocker l(_animatedImageLock); _animatedImagePaused = animatedImagePaused; ASPerformBlockOnMainThread(^{ if (animatedImagePaused) { @@ -63,7 +66,7 @@ - (BOOL)animatedImagePaused { - ASDN::MutexLocker l(_animatedImagePausedLock); + ASDN::MutexLocker l(_animatedImageLock); return _animatedImagePaused; } @@ -84,7 +87,7 @@ - (void)animatedImageFileReady { - dispatch_async(dispatch_get_main_queue(), ^{ + ASPerformBlockOnMainThread(^{ [self startAnimating]; }); } @@ -147,18 +150,6 @@ } } -- (void)__enterHierarchy -{ - [super __enterHierarchy]; - [self startAnimating]; -} - -- (void)__exitHierarchy -{ - [super __exitHierarchy]; - [self stopAnimating]; -} - - (void)displayLinkFired:(CADisplayLink *)displayLink { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index 8ce01b6397..9e4a8024dd 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -8,6 +8,8 @@ #import +#import "ASImageProtocols.h" + NS_ASSUME_NONNULL_BEGIN /** @@ -110,6 +112,22 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); */ - (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock; +/** + * @abstract The animated image to playback + * + * @discussion Set this to an object which conforms to ASAnimatedImageProtocol + * to have the ASImageNode playback an animated image. + */ +@property (nullable, atomic, strong) id animatedImage; + +/** + * @abstract Pause the playback of an animated image. + * + * @discussion Set to YES to pause playback of an animated image and NO to resume + * playback. + */ +@property (atomic, assign) BOOL animatedImagePaused; + @end diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 7bb5eaa7d3..0fddf08aa4 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -16,7 +16,6 @@ #import #import #import -#import #import #import "ASImageNode+CGExtras.h" diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 6a8e37cbdb..c3b05421cb 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -15,7 +15,6 @@ #import "ASThread.h" #import "ASInternalHelpers.h" #import "ASImageContainerProtocolCategories.h" -#import "ASImageNode+AnimatedImage.h" #if PIN_REMOTE_IMAGE #import "ASPINRemoteImageDownloader.h" diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index ad24302d62..4035c60416 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -11,7 +11,6 @@ #import #import -#import #import #import #import diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index 3dc5568d06..c5079760d0 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -136,21 +136,62 @@ withDownloadIdentifier:(id)downloadIdentifier; @protocol ASAnimatedImageProtocol +/** + @abstract Should be called when the objects cover image is ready. + @param coverImageReadyCallback a block which receives the cover image. + */ @property (nonatomic, strong, readwrite) void (^coverImageReadyCallback)(UIImage *coverImage); @required +/** + @abstract Return the objects's cover image. + */ @property (nonatomic, readonly) UIImage *coverImage; +/** + @abstract Return a boolean to indicate that the cover image is ready. + */ @property (nonatomic, readonly) BOOL coverImageReady; +/** + @abstract Return the total duration of the animated image's playback. + */ @property (nonatomic, readonly) CFTimeInterval totalDuration; +/** + @abstract Return the interval at which playback should occur. Will be set to a CADisplayLink's frame interval. + */ @property (nonatomic, readonly) NSUInteger frameInterval; +/** + @abstract Return the total number of loops the animated image should play or 0 to loop infinitely. + */ @property (nonatomic, readonly) size_t loopCount; +/** + @abstract Return the total number of frames in the animated image. + */ @property (nonatomic, readonly) size_t frameCount; +/** + @abstract Return YES when playback is ready to occur. + */ @property (nonatomic, readonly) BOOL playbackReady; +/** + @abstract Return any error that has occured. Playback will be paused if this returns non-nil. + */ +@property (nonatomic, readonly) NSError *error; +/** + @abstract Should be called when playback is ready. + */ @property (nonatomic, strong, readwrite) dispatch_block_t playbackReadyCallback; +/** + @abstract Return the image at a given index. + */ - (CGImageRef)imageAtIndex:(NSUInteger)index; +/** + @abstract Return the duration at a given index. + */ - (CFTimeInterval)durationAtIndex:(NSUInteger)index; +/** + @abstract Clear any cached data. Called when playback is paused. + */ - (void)clearAnimatedImageCache; @end diff --git a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h index 2814ef9ddc..d122c4e5d4 100644 --- a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h +++ b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h @@ -11,7 +11,6 @@ @interface ASImageNode () { ASDN::RecursiveMutex _animatedImageLock; - ASDN::RecursiveMutex _animatedImagePausedLock; ASDN::Mutex _displayLinkLock; id _animatedImage; BOOL _animatedImagePaused; diff --git a/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m b/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m deleted file mode 100644 index b4a601de56..0000000000 --- a/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.m +++ /dev/null @@ -1,45 +0,0 @@ -// -// AppDelegate.m -// ASAnimatedImage -// -// Created by Garrett Moon on 3/22/16. -// Copyright © 2016 Facebook, Inc. All rights reserved. -// - -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // 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. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // 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. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // 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. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // 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. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.h b/examples/AnimatedGIF/ASAnimatedImage/AppDelegate.h similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/AppDelegate.h rename to examples/AnimatedGIF/ASAnimatedImage/AppDelegate.h diff --git a/examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m b/examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m new file mode 100644 index 0000000000..fc2016ef4b --- /dev/null +++ b/examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m @@ -0,0 +1,23 @@ +// +// AppDelegate.m +// ASAnimatedImage +// +// Created by Garrett Moon on 3/22/16. +// Copyright © 2016 Facebook, Inc. All rights reserved. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +@end diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/AnimatedGIF/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json rename to examples/AnimatedGIF/ASAnimatedImage/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard b/examples/AnimatedGIF/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard rename to examples/AnimatedGIF/ASAnimatedImage/Base.lproj/LaunchScreen.storyboard diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/Main.storyboard b/examples/AnimatedGIF/ASAnimatedImage/Base.lproj/Main.storyboard similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/Base.lproj/Main.storyboard rename to examples/AnimatedGIF/ASAnimatedImage/Base.lproj/Main.storyboard diff --git a/examples/ASAnimatedImage/ASAnimatedImage/Info.plist b/examples/AnimatedGIF/ASAnimatedImage/Info.plist similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/Info.plist rename to examples/AnimatedGIF/ASAnimatedImage/Info.plist diff --git a/examples/ASAnimatedImage/ASAnimatedImage/ViewController.h b/examples/AnimatedGIF/ASAnimatedImage/ViewController.h similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/ViewController.h rename to examples/AnimatedGIF/ASAnimatedImage/ViewController.h diff --git a/examples/ASAnimatedImage/ASAnimatedImage/ViewController.m b/examples/AnimatedGIF/ASAnimatedImage/ViewController.m similarity index 87% rename from examples/ASAnimatedImage/ASAnimatedImage/ViewController.m rename to examples/AnimatedGIF/ASAnimatedImage/ViewController.m index 9087dfdd5d..658a93cb7a 100644 --- a/examples/ASAnimatedImage/ASAnimatedImage/ViewController.m +++ b/examples/AnimatedGIF/ASAnimatedImage/ViewController.m @@ -29,9 +29,4 @@ [self.view addSubnode:imageNode]; } -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - @end diff --git a/examples/ASAnimatedImage/ASAnimatedImage/main.m b/examples/AnimatedGIF/ASAnimatedImage/main.m similarity index 100% rename from examples/ASAnimatedImage/ASAnimatedImage/main.m rename to examples/AnimatedGIF/ASAnimatedImage/main.m diff --git a/examples/ASAnimatedImage/Podfile b/examples/AnimatedGIF/Podfile similarity index 100% rename from examples/ASAnimatedImage/Podfile rename to examples/AnimatedGIF/Podfile diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/project.pbxproj b/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj similarity index 100% rename from examples/ASAnimatedImage/Sample.xcodeproj/project.pbxproj rename to examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/AnimatedGIF/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/ASAnimatedImage/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to examples/AnimatedGIF/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/ASAnimatedImage/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/AnimatedGIF/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme similarity index 100% rename from examples/ASAnimatedImage/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme rename to examples/AnimatedGIF/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme diff --git a/examples/ASAnimatedImage/Sample.xcworkspace/contents.xcworkspacedata b/examples/AnimatedGIF/Sample.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/ASAnimatedImage/Sample.xcworkspace/contents.xcworkspacedata rename to examples/AnimatedGIF/Sample.xcworkspace/contents.xcworkspacedata From 68de6a6391ecd8c4b1678ce160ffb7c57a98d874 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 19 Apr 2016 15:06:00 -0700 Subject: [PATCH 13/17] Fix "[NSProxy methodSignatureForSelector:] called!" crash --- AsyncDisplayKit/Details/ASDelegateProxy.m | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 3034a83d86..b4f6977525 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -131,6 +131,31 @@ } } +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + // Check for a compiled definition for the selector + NSMethodSignature *methodSignature = nil; + if ([self interceptsSelector:aSelector]) { + methodSignature = [[_interceptor class] instanceMethodSignatureForSelector:aSelector]; + } else { + methodSignature = [[_target class] instanceMethodSignatureForSelector:aSelector]; + } + + // Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature + // from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found. + // This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method + // returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines + // the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will + // suffice. Since the -forwardInvocation call will do nothing if the delegate does not respond to the selector, + // the dud NSMethodSignature simply gets us around the exception. + return methodSignature ?: [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + // If we are down here this means _interceptor and _target where nil. Just don't do anything to prevent a crash +} + - (BOOL)interceptsSelector:(SEL)selector { ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses."); From 42174210c5a9b4b07c97e1d33eb9c98445bbfdf3 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 19 Apr 2016 16:07:42 -0700 Subject: [PATCH 14/17] Follow up for ASCollectionNode and ASTableNode background color fix --- AsyncDisplayKit/ASDisplayNode.mm | 4 ++-- .../Private/ASDisplayNode+UIViewBridge.mm | 14 ++++---------- AsyncDisplayKit/Private/ASDisplayNodeInternal.h | 2 +- AsyncDisplayKit/Private/_ASPendingState.mm | 8 ++++---- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7ad4ee3b6c..cc9000afc7 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -85,7 +85,7 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties // like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only. -BOOL ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(ASDisplayNodeFlags flags) +BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags) { return flags.synchronous && !flags.layerBacked; } @@ -961,7 +961,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (self.layerBacked) { [_pendingViewState applyToLayer:self.layer]; } else { - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(_flags); + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags); [_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index c777857485..1ec0bc5795 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -9,17 +9,10 @@ #import "_ASCoreAnimationExtras.h" #import "_ASPendingState.h" #import "ASInternalHelpers.h" -#import "ASAssert.h" #import "ASDisplayNodeInternal.h" -#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" -#import "ASEqualityHelpers.h" #import "ASPendingStateController.h" -#import "ASThread.h" -#import "ASTextNode.h" -#import "ASTableNode.h" /** * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. @@ -240,7 +233,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: struct ASDisplayNodeFlags flags = _flags; - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(flags); + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(flags); BOOL nodeLoaded = __loaded(self); BOOL isMainThread = ASDisplayNodeThreadIsMain(); @@ -583,11 +576,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo if (shouldApply) { CGColorRef oldBackgroundCGColor = _layer.backgroundColor; - _layer.backgroundColor = newBackgroundCGColor; - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(_flags); + BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags); if (specialPropertiesHandling) { _view.backgroundColor = newBackgroundColor; + } else { + _layer.backgroundColor = newBackgroundCGColor; } if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) { diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index ae7616add2..4828b8bec6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -28,7 +28,7 @@ struct ASDisplayNodeFlags; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -BOOL ASDisplayNodeNeedsSpecialPropertiesSettingHandlingForFlags(ASDisplayNodeFlags flags); +BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags); /// Get the pending view state for the node, creating one if needed. _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node); diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index 303bf2a58c..97017907fb 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -790,12 +790,12 @@ static UIColor *defaultTintColor = nil; view.clipsToBounds = clipsToBounds; if (flags.setBackgroundColor) { - // Set the background color to the layer as in the UIView bridge we use this value as background color - layer.backgroundColor = backgroundColor; - - // We have to make sure certain nodes get the background color call directly + // We have to make sure certain nodes get the background color call directly set if (specialPropertiesHandling) { view.backgroundColor = [UIColor colorWithCGColor:backgroundColor]; + } else { + // Set the background color to the layer as in the UIView bridge we use this value as background color + layer.backgroundColor = backgroundColor; } } From 3a20c6a1f4af52ba9e044359394ddd566eba4bdb Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 12 Apr 2016 17:21:36 -0700 Subject: [PATCH 15/17] Add caching for delegate and datasource calls in ASCollectionView and ASTableView --- AsyncDisplayKit/ASCollectionView.mm | 86 +++++++++++++++++++++-------- AsyncDisplayKit/ASTableView.mm | 79 +++++++++++++++++++------- 2 files changed, 123 insertions(+), 42 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0835dcfbe1..024185cff5 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -106,9 +106,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; NSMutableArray *_batchUpdateBlocks; BOOL _asyncDataFetchingEnabled; - BOOL _asyncDelegateImplementsScrollviewDidScroll; - BOOL _asyncDataSourceImplementsConstrainedSizeForNode; - BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath; _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only BOOL _isDeallocating; @@ -133,6 +130,25 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; * The collection view never queried your data source before the update to see that it actually had 0 items. */ BOOL _superIsPendingDataLoad; + + struct { + unsigned int asyncDelegateScrollViewDidScroll:1; + unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1; + unsigned int asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath:1; + unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath:1; + unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated:1; + unsigned int asyncDelegateCollectionViewWillBeginBatchFetchWithContext:1; + unsigned int asyncDelegateShouldBatchFetchForCollectionView:1; + } _asyncDelegateFlags; + + struct { + unsigned int asyncDataSourceConstrainedSizeForNode:1; + unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; + unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; + unsigned int asyncDataSourceCollectionViewLockDataSource:1; + unsigned int asyncDataSourceCollectionViewUnlockDataSource:1; + unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; + } _asyncDataSourceFlags; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -333,16 +349,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (asyncDataSource == nil) { _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceImplementsConstrainedSizeForNode = NO; - _asyncDataSourceImplementsNodeBlockForItemAtIndexPath = NO; + + _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = NO; + _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = NO; + _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = NO; + _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = NO; + _asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = NO; + _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = NO; } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - _asyncDataSourceImplementsConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; - _asyncDataSourceImplementsNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; + + _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; + _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]; + _asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]; + _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: - ASDisplayNodeAssertTrue(_asyncDataSourceImplementsNodeBlockForItemAtIndexPath || [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]); + ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode || _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath); } super.dataSource = (id)_proxyDataSource; @@ -363,11 +389,25 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateImplementsScrollviewDidScroll = NO; + + _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = NO; + _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = NO; + _asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = NO; + _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = NO; + _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = NO; + _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = NO; + _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = NO; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - _asyncDelegateImplementsScrollviewDidScroll = ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)] ? 1 : 0); + + _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; + _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; + _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]; } super.delegate = (id)_proxyDelegate; @@ -566,7 +606,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASCellNode *cellNode = [cell node]; cellNode.scrollView = collectionView; - if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath) { [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } @@ -586,7 +626,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASCellNode *cellNode = [cell node]; - if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath) { ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; } @@ -597,7 +637,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated) { [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; } #pragma clang diagnostic pop @@ -620,7 +660,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; inScrollView:scrollView withCellFrame:collectionCell.frame]; } - if (_asyncDelegateImplementsScrollviewDidScroll) { + if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) { [_asyncDelegate scrollViewDidScroll:scrollView]; } } @@ -637,7 +677,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) { [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; } } @@ -747,8 +787,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; - if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) { + BOOL canFetch = _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext; + if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView) { return [_asyncDelegate shouldBatchFetchForCollectionView:self]; } else { return canFetch; @@ -788,7 +828,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)_beginBatchFetching { [_batchContext beginBatchFetching]; - if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { + if (_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -800,7 +840,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { - if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) { + if (!_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); __weak __typeof__(self) weakSelf = self; @@ -842,7 +882,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't // implement a custom inspector. - if (_asyncDataSourceImplementsConstrainedSizeForNode) { + if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) { constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; } else { CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; @@ -863,7 +903,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { + if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView) { return [_asyncDataSource numberOfSectionsInCollectionView:self]; } else { return 1; @@ -875,7 +915,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); self.asyncDataSourceLocked = YES; - if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { + if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) { [_asyncDataSource collectionViewLockDataSource:self]; } } @@ -885,7 +925,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); self.asyncDataSourceLocked = NO; - if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { + if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) { [_asyncDataSource collectionViewUnlockDataSource:self]; } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 6718932297..17068f47cd 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -112,9 +112,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL _ignoreNodesConstrainedWidthChange; BOOL _queuedNodeHeightUpdate; BOOL _isDeallocating; - BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath; - BOOL _asyncDelegateImplementsScrollviewDidScroll; NSMutableSet *_cellsForVisibilityUpdates; + + struct { + unsigned int asyncDelegateScrollViewDidScroll:1; + unsigned int asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath:1; + unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath:1; + unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated:1; + unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1; + unsigned int asyncDelegateTableViewWillBeginBatchFetchWithContext:1; + unsigned int asyncDelegateShouldBatchFetchForTableView:1; + } _asyncDelegateFlags; + + struct { + unsigned int asyncDataSourceNumberOfSectionsInTableView:1; + unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1; + unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1; + unsigned int asyncDataSourceTableViewLockDataSource:1; + unsigned int asyncDataSourceTableViewUnlockDataSource:1; + } _asyncDataSourceFlags; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -260,13 +276,24 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (asyncDataSource == nil) { _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - _dataSourceImplementsNodeBlockForRowAtIndexPath = NO; + + _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = NO; + _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = NO; + _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = NO; + _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = NO; + _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = 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]; + + _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]; + + // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath: + ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath); } super.dataSource = (id)_proxyDataSource; @@ -287,11 +314,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateImplementsScrollviewDidScroll = NO; + + _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = NO; + _asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath = NO; + _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath = NO; + _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated = NO; + _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = NO; + _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext = NO; + _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = NO; } else { _asyncDelegate = asyncDelegate; - _asyncDelegateImplementsScrollviewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; + + _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; + _asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; + _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; } super.delegate = (id)_proxyDelegate; @@ -584,7 +625,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASCellNode *cellNode = [cell node]; cellNode.scrollView = tableView; - if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath) { [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } @@ -609,7 +650,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; - if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath) { ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; } @@ -620,7 +661,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) { + if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated) { [_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath]; } #pragma clang diagnostic pop @@ -642,7 +683,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; inScrollView:scrollView withCellFrame:tableCell.frame]; } - if (_asyncDelegateImplementsScrollviewDidScroll) { + if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) { [_asyncDelegate scrollViewDidScroll:scrollView]; } } @@ -659,7 +700,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) { [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; } } @@ -722,8 +763,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; - if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]) { + BOOL canFetch = _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext; + if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView) { return [_asyncDelegate shouldBatchFetchForTableView:self]; } else { return canFetch; @@ -763,7 +804,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)_beginBatchFetching { [_batchContext beginBatchFetching]; - if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { + if (_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -1013,7 +1054,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; self.asyncDataSourceLocked = YES; - if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) { + if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) { [_asyncDataSource tableViewLockDataSource:self]; } } @@ -1024,7 +1065,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; self.asyncDataSourceLocked = NO; - if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) { + if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) { [_asyncDataSource tableViewUnlockDataSource:self]; } } @@ -1036,7 +1077,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { + if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView) { return [_asyncDataSource numberOfSectionsInTableView:self]; } else { return 1; // default section number From d9f16e0acfcc34d7b8a9a0a574591ec7023cc437 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 12 Apr 2016 17:22:26 -0700 Subject: [PATCH 16/17] Use _performingBatchUpdates for _layoutFacilitator batched parameter --- AsyncDisplayKit/ASCollectionView.mm | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 024185cff5..2055cce528 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1042,8 +1042,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates]; if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; [_batchUpdateBlocks addObject:^{ [super insertItemsAtIndexPaths:indexPaths]; }]; @@ -1063,13 +1063,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates]; if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES]; [_batchUpdateBlocks addObject:^{ [super deleteItemsAtIndexPaths:indexPaths]; }]; } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; @@ -1084,13 +1083,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates]; if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; [_batchUpdateBlocks addObject:^{ [super insertSections:indexSet]; }]; } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; @@ -1105,13 +1103,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates]; if (_performingBatchUpdates) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES]; [_batchUpdateBlocks addObject:^{ [super deleteSections:indexSet]; }]; } else { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super deleteSections:indexSet]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; From 8f1beb8bf6d78f6fe5ee4c2ed7ab73cfc570d755 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 18 Apr 2016 15:23:44 -0700 Subject: [PATCH 17/17] Addressed comments - Use memset to clear flags - Fix check for asyncDataSourceNodeForItemAtIndexPath - Fix selector for asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath - Fix multiple collectionViewWillEditCellsAtIndexPaths:batched: calls --- AsyncDisplayKit/ASCollectionView.mm | 22 ++++++---------------- AsyncDisplayKit/ASTableView.mm | 16 +++------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 2055cce528..950cba8f76 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -143,6 +143,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; struct { unsigned int asyncDataSourceConstrainedSizeForNode:1; + unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; unsigned int asyncDataSourceCollectionViewLockDataSource:1; @@ -350,17 +351,13 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = NO; - _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = NO; - _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = NO; - _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = NO; - _asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = NO; - _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = NO; + memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags)); } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]; @@ -368,7 +365,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: - ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode || _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath); + ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); } super.dataSource = (id)_proxyDataSource; @@ -390,13 +387,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = NO; - _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = NO; - _asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = NO; - _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = NO; - _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = NO; - _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = NO; - _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = NO; + memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags)); } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; @@ -405,7 +396,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; _asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]; _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]; - _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]; + _asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]; _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]; } @@ -1048,7 +1039,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [super insertItemsAtIndexPaths:indexPaths]; }]; } else { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 17068f47cd..5fe3d06694 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -277,18 +277,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = NO; - _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = NO; - _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = NO; - _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = NO; - _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = NO; + memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags)); } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; - _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]; _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]; @@ -315,13 +311,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateFlags.asyncDelegateScrollViewDidScroll = NO; - _asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath = NO; - _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath = NO; - _asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated = NO; - _asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = NO; - _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext = NO; - _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = NO; + memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags)); } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];