From d7b128e19bb00fcc4d8c5b6aec60215cb50edc1c Mon Sep 17 00:00:00 2001 From: Rajinder Ramgarhia Date: Sun, 20 Dec 2015 21:25:59 -0500 Subject: [PATCH 01/19] ASButtonNode content alignement properties --- AsyncDisplayKit.xcodeproj/project.pbxproj | 17 ++++++++++++++++- AsyncDisplayKit/ASButtonNode.h | 11 +++++++++++ AsyncDisplayKit/ASButtonNode.mm | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 2e0296d33b..c07972fce7 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1523,7 +1523,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */, + 5ADEA7587189397768F2B36C /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1668,6 +1668,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 5ADEA7587189397768F2B36C /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index bf2227eae1..55b3948f2e 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -31,6 +31,17 @@ typedef enum : NSUInteger { */ @property (nonatomic, assign) BOOL laysOutHorizontally; +/** Horizontally align content(text or image). + Defaults to ASAlignmentMiddle. + */ +@property (nonatomic, assign) ASHorizontalAlignment contentHorizontalAlignment; + +/** Vertically align content(text or image). + Defaults to ASAlignmentCenter. + */ +@property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment; + + - (NSAttributedString *)attributedTitleForState:(ASButtonState)state; - (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state; diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 1801a03527..17431576ce 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -195,8 +195,8 @@ ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; stack.direction = self.laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; stack.spacing = self.contentSpacing; - stack.justifyContent = ASStackLayoutJustifyContentCenter; - stack.alignItems = ASStackLayoutAlignItemsCenter; + stack.horizontalAlignment = self.contentHorizontalAlignment ? self.contentHorizontalAlignment : ASAlignmentMiddle; + stack.verticalAlignment = self.contentVerticalAlignment ? self.contentVerticalAlignment : ASAlignmentCenter; NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; if (self.imageNode.image) { From 108ce6d8fcfbf394d8bfca740060cc9176b3224c Mon Sep 17 00:00:00 2001 From: Rajinder Ramgarhia Date: Mon, 21 Dec 2015 10:03:32 -0500 Subject: [PATCH 02/19] Set horizontal & vertical alignment default to center. Use ivar to set layout. --- AsyncDisplayKit/ASButtonNode.h | 4 ++-- AsyncDisplayKit/ASButtonNode.mm | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index 55b3948f2e..57b7caf130 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -31,12 +31,12 @@ typedef enum : NSUInteger { */ @property (nonatomic, assign) BOOL laysOutHorizontally; -/** Horizontally align content(text or image). +/** Horizontally align content (text or image). Defaults to ASAlignmentMiddle. */ @property (nonatomic, assign) ASHorizontalAlignment contentHorizontalAlignment; -/** Vertically align content(text or image). +/** Vertically align content (text or image). Defaults to ASAlignmentCenter. */ @property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment; diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 17431576ce..196216a6d5 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -39,6 +39,9 @@ _titleNode = [[ASTextNode alloc] init]; _imageNode = [[ASImageNode alloc] init]; + _contentHorizontalAlignment = ASAlignmentMiddle; + _contentVerticalAlignment = ASAlignmentCenter; + [self addSubnode:_titleNode]; [self addSubnode:_imageNode]; @@ -195,8 +198,8 @@ ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; stack.direction = self.laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; stack.spacing = self.contentSpacing; - stack.horizontalAlignment = self.contentHorizontalAlignment ? self.contentHorizontalAlignment : ASAlignmentMiddle; - stack.verticalAlignment = self.contentVerticalAlignment ? self.contentVerticalAlignment : ASAlignmentCenter; + stack.horizontalAlignment = _contentHorizontalAlignment; + stack.verticalAlignment = _contentVerticalAlignment; NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; if (self.imageNode.image) { From 065625f246f09075c12840869ec01215773c0395 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Dec 2015 21:46:46 -0800 Subject: [PATCH 03/19] Make ASCollectionView always create an ASCollectionNode. Add visibilityDidChange:, interfaceStateDidChange:fromState: --- AsyncDisplayKit/ASCollectionNode.m | 11 +- AsyncDisplayKit/ASCollectionView.h | 36 ++--- AsyncDisplayKit/ASCollectionView.mm | 130 +++++++++--------- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 123 ++++++++--------- AsyncDisplayKit/ASDisplayNode.mm | 14 +- AsyncDisplayKit/ASViewController.h | 11 +- AsyncDisplayKit/ASViewController.m | 5 + .../Private/ASDisplayNode+FrameworkPrivate.h | 3 + .../Sample.xcodeproj/project.pbxproj | 16 --- 9 files changed, 180 insertions(+), 169 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index cd0b7367c6..eb0ddd3064 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -9,6 +9,10 @@ #import "ASCollectionNode.h" #import "ASDisplayNode+Subclasses.h" +@interface ASCollectionView (Internal) +- (ASCollectionView *)_initWithCollectionViewLayout:(UICollectionViewLayout *)layout; +@end + @implementation ASCollectionNode - (instancetype)init @@ -20,7 +24,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] initWithCollectionViewLayout:layout]; }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] _initWithCollectionViewLayout:layout]; }]) { return self; } return nil; @@ -31,6 +35,11 @@ return (ASCollectionView *)[super view]; } +- (void)visibilityDidChange:(BOOL)isVisible +{ + +} + - (void)clearContents { [super clearContents]; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 31b3722d0c..8d715e3e68 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -12,7 +12,7 @@ #import #import #import - +#import @class ASCellNode; @protocol ASCollectionViewDataSource; @@ -27,6 +27,11 @@ */ @interface ASCollectionView : UICollectionView +/** + * Initializer. + * + * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. + */ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; @property (nonatomic, weak) id asyncDataSource; @@ -52,27 +57,6 @@ */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; -/** - * Initializer. - * - * @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview - * in which you plan to add it. This frame is passed to the superclass during initialization. - * - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. - * Must not be nil. - * - * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - * - * @discussion If asyncDataFetching is enabled, the `ASCollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and - * `collectionView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically - * from calling thread. - * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for - * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, - * we will lock the data source through `collectionViewLockDataSource`, and unlock it by `collectionViewUnlockDataSource` after the data fetching. - * The application should not update the data source while the data source is locked, to keep data consistence. - */ -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled; - /** * The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called. * @@ -429,4 +413,10 @@ */ - (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section; -@end \ No newline at end of file +@end + +@interface ASCollectionView (Deprecated) + +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED; + +@end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ffd1c66005..0aeefb2764 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,6 +9,7 @@ #import "ASAssert.h" #import "ASBatchFetching.h" #import "ASCollectionView.h" +#import "ASCollectionNode.h" #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" @@ -31,7 +32,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; */ static BOOL _isInterceptedSelector(SEL sel) { - return ( + return ( // handled by ASCollectionView node<->cell machinery sel == @selector(collectionView:cellForItemAtIndexPath:) || sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) || @@ -44,7 +45,7 @@ static BOOL _isInterceptedSelector(SEL sel) // used for ASRangeController visibility updates sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || - + // used for batch fetching API sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) ); @@ -70,7 +71,7 @@ static BOOL _isInterceptedSelector(SEL sel) if (!self) { return nil; } - + ASDisplayNodeAssert(target, @"target must not be nil"); ASDisplayNodeAssert(interceptor, @"interceptor must not be nil"); @@ -84,7 +85,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - + return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]); } @@ -92,7 +93,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - + if (_isInterceptedSelector(aSelector)) { return _interceptor; } @@ -140,21 +141,21 @@ static BOOL _isInterceptedSelector(SEL sel) @interface ASCollectionView () { _ASCollectionViewProxy *_proxyDataSource; _ASCollectionViewProxy *_proxyDelegate; - + ASCollectionDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; - + BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; - + BOOL _asyncDataFetchingEnabled; BOOL _asyncDelegateImplementsInsetSection; BOOL _collectionViewLayoutImplementsInsetSection; BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _queuedNodeSizeUpdate; - + ASBatchContext *_batchContext; CGSize _maxSizeForNodesConstrainedSize; @@ -172,7 +173,7 @@ static BOOL _isInterceptedSelector(SEL sel) * You will get an assertion failure saying `Invalid number of items in section 0. * The number of items after the update (1) must be equal to the number of items before the update (1) plus or minus the items added and removed (1 added, 0 removed).` * The collection view never queried your data source before the update to see that it actually had 0 items. - */ + */ BOOL _superIsPendingDataLoad; } @@ -187,48 +188,53 @@ static BOOL _isInterceptedSelector(SEL sel) - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - return [self initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + return [self initWithFrame:CGRectZero collectionViewLayout:layout]; } - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { - return [self initWithFrame:frame collectionViewLayout:layout asyncDataFetching:NO]; +// ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout]; +// collectionNode.frame = frame; +// return collectionNode.view; + return [self _initWithFrame:frame collectionViewLayout:layout]; } +// FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + return [self initWithFrame:frame collectionViewLayout:layout]; +} + +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; - // FIXME: asyncDataFetching is currently unreliable for some use cases. - // https://github.com/facebook/AsyncDisplayKit/issues/385 - asyncDataFetchingEnabled = NO; - _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; - + _rangeController = [[ASRangeController alloc] init]; _rangeController.dataSource = self; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - - _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; + + _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO]; _dataController.delegate = _rangeController; _dataController.dataSource = self; _batchContext = [[ASBatchContext alloc] init]; - + _leadingScreensForBatching = 1.0; - - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + + _asyncDataFetchingEnabled = NO; _asyncDataSourceLocked = NO; - + _performingBatchUpdates = NO; _batchUpdateBlocks = [NSMutableArray array]; - + _superIsPendingDataLoad = YES; _collectionViewLayoutImplementsInsetSection = [layout respondsToSelector:@selector(sectionInset)]; - + _maxSizeForNodesConstrainedSize = self.bounds.size; // If the initial size is 0, expect a size change very soon which is part of the initial configuration // and should not trigger a relayout. @@ -262,13 +268,13 @@ static BOOL _isInterceptedSelector(SEL sel) */ - (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector { - if (_flowLayoutInspector == nil) { - UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; - ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); - _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self - flowLayout:layout]; - } - return _flowLayoutInspector; + if (_flowLayoutInspector == nil) { + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); + _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self + flowLayout:layout]; + } + return _flowLayoutInspector; } #pragma mark - @@ -314,7 +320,7 @@ static BOOL _isInterceptedSelector(SEL sel) // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. - + if (asyncDataSource == nil) { super.dataSource = nil; _asyncDataSource = nil; @@ -334,7 +340,7 @@ static BOOL _isInterceptedSelector(SEL sel) // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. - + if (asyncDelegate == nil) { // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes // in UIScrollViewAccessibility. @@ -348,7 +354,7 @@ static BOOL _isInterceptedSelector(SEL sel) super.delegate = (id)_proxyDelegate; _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); } - + [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; } @@ -409,7 +415,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); - + [_dataController beginUpdates]; updates(); [_dataController endUpdatesAnimated:animated completion:completion]; @@ -487,7 +493,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; - + ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; cell.node = node; [_rangeController configureContentView:cell.contentView forCellNode:node]; @@ -524,7 +530,7 @@ static BOOL _isInterceptedSelector(SEL sel) CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; return [self scrollDirectionForVelocity:scrollVelocity]; } - + - (ASScrollDirection)scrollDirectionForVelocity:(CGPoint)scrollVelocity { ASScrollDirection direction = ASScrollDirectionNone; @@ -544,7 +550,7 @@ static BOOL _isInterceptedSelector(SEL sel) direction |= ASScrollDirectionUp; } } - + return direction; } @@ -623,7 +629,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { [self handleBatchFetchScrollingToOffset:*targetContentOffset]; - + if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; } @@ -643,11 +649,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - + if (![self shouldBatchFetch]) { return; } - + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { [_batchContext beginBatchFetching]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -695,7 +701,7 @@ static BOOL _isInterceptedSelector(SEL sel) } constrainedSize = ASSizeRangeMake(CGSizeZero, maxSize); } - + UIEdgeInsets sectionInset = UIEdgeInsetsZero; if (_collectionViewLayoutImplementsInsetSection) { sectionInset = [(UICollectionViewFlowLayout *)self.collectionViewLayout sectionInset]; @@ -704,7 +710,7 @@ static BOOL _isInterceptedSelector(SEL sel) if (_asyncDelegateImplementsInsetSection) { sectionInset = [(id)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section]; } - + if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right); //ignore insets for FLT_MAX so FLT_MAX can be compared against @@ -718,7 +724,7 @@ static BOOL _isInterceptedSelector(SEL sel) constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom); } } - + return constrainedSize; } @@ -738,7 +744,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)dataControllerLockDataSource { ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); - + self.asyncDataSourceLocked = YES; if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { [_asyncDataSource collectionViewLockDataSource:self]; @@ -748,7 +754,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)dataControllerUnlockDataSource { ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); - + self.asyncDataSourceLocked = NO; if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { [_asyncDataSource collectionViewUnlockDataSource:self]; @@ -817,7 +823,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)rangeController:(ASRangeController *)rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); - + if (!self.asyncDataSource || _superIsPendingDataLoad) { if (completion) { completion(NO); @@ -832,7 +838,7 @@ static BOOL _isInterceptedSelector(SEL sel) } } completion:completion]; }); - + [_batchUpdateBlocks removeAllObjects]; _performingBatchUpdates = NO; } @@ -840,11 +846,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - + if (!self.asyncDataSource || _superIsPendingDataLoad) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + if (_performingBatchUpdates) { [_batchUpdateBlocks addObject:^{ [super insertItemsAtIndexPaths:indexPaths]; @@ -859,11 +865,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - + if (!self.asyncDataSource || _superIsPendingDataLoad) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + if (_performingBatchUpdates) { [_batchUpdateBlocks addObject:^{ [super deleteItemsAtIndexPaths:indexPaths]; @@ -878,11 +884,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - + if (!self.asyncDataSource || _superIsPendingDataLoad) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + if (_performingBatchUpdates) { [_batchUpdateBlocks addObject:^{ [super insertSections:indexSet]; @@ -897,11 +903,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - + if (!self.asyncDataSource || _superIsPendingDataLoad) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + if (_performingBatchUpdates) { [_batchUpdateBlocks addObject:^{ [super deleteSections:indexSet]; @@ -918,11 +924,11 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); - + if (!sizeChanged || _queuedNodeSizeUpdate) { return; } - + _queuedNodeSizeUpdate = YES; [self performSelector:@selector(requeryNodeSizes) withObject:nil @@ -934,7 +940,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)requeryNodeSizes { _queuedNodeSizeUpdate = NO; - + [super performBatchUpdates:^{} completion:nil]; } @@ -944,7 +950,7 @@ static BOOL _isInterceptedSelector(SEL sel) { for (NSArray *section in [_dataController completedNodes]) { for (ASDisplayNode *node in section) { - [node recursivelyClearContents]; + [node exitInterfaceState:ASInterfaceStateDisplay]; } } } @@ -953,7 +959,7 @@ static BOOL _isInterceptedSelector(SEL sel) { for (NSArray *section in [_dataController completedNodes]) { for (ASDisplayNode *node in section) { - [node recursivelyClearFetchedData]; + [node exitInterfaceState:ASInterfaceStateFetchData]; } } } diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index b0bb3b0e0f..108dbdd322 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -37,35 +37,8 @@ @interface ASDisplayNode (Subclassing) - -/** @name View Configuration */ - - -/** - * @return The view class to use when creating a new display node instance. Defaults to _ASDisplayView. - */ -+ (Class)viewClass; - - /** @name Properties */ - -/** - * @abstract The scale factor to apply to the rendering. - * - * @discussion Use setNeedsDisplayAtScale: to set a value and then after display, the display node will set the layer's - * contentsScale. This is to prevent jumps when re-rasterizing at a different contentsScale. - * Read this property if you need to know the future contentsScale of your layer, eg in drawParameters. - * - * @see setNeedsDisplayAtScale: - */ -@property (nonatomic, assign, readonly) CGFloat contentsScaleForDisplay; - -/** - * @abstract Whether the view or layer of this display node is currently in a window - */ -@property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy; - /** * @abstract Return the calculated layout. * @@ -190,10 +163,9 @@ * * @note Called on the display queue and/or main queue (MUST BE THREAD SAFE) */ -+ (void)drawRect:(CGRect)bounds - withParameters:(id)parameters - isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock - isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters + isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock + isRasterizing:(BOOL)isRasterizing; /** * @summary Delegate override to provide new layer contents as a UIImage. @@ -236,6 +208,33 @@ */ - (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER; +/** @name Observing node-related changes */ + +/** + * @abstract Called whenever any bit in the ASInterfaceState bitfield is changed. + * + * @discussion Subclasses may use this to monitor when they become visible, should free cached data, and much more. + * @see ASInterfaceState + */ +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState; + +- (void)visibilityDidChange:(BOOL)isVisible; + +/** + * Called just before the view is added to a window. + */ +- (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; + +/** + * Called after the view is removed from the window. + */ +- (void)didExitHierarchy ASDISPLAYNODE_REQUIRES_SUPER; + +/** + * @abstract Whether the view or layer of this display node is currently in a window + */ +@property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy; + /** * @abstract Indicates that the node should fetch any external data, such as images. * @@ -245,6 +244,23 @@ */ - (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER; +/** + * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. + * + * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearFetchedData or + * selectively clear fetched data. + */ +- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER; + +/** + * Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers + * on the current node. + * + * @discussion Called by -recursivelyClearContents. Base class implements self.contents = nil, clearing any backing + * store, for asynchronous regeneration when needed. + */ +- (void)clearContents ASDISPLAYNODE_REQUIRES_SUPER; + /** * @abstract Indicates that the receiver is about to display its subnodes. This method is not called if there are no * subnodes present. @@ -267,7 +283,6 @@ */ - (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER; - /** * @abstract Marks the receiver's bounds as needing to be redrawn, with a scale value. * @@ -295,6 +310,17 @@ */ - (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale; +/** + * @abstract The scale factor to apply to the rendering. + * + * @discussion Use setNeedsDisplayAtScale: to set a value and then after display, the display node will set the layer's + * contentsScale. This is to prevent jumps when re-rasterizing at a different contentsScale. + * Read this property if you need to know the future contentsScale of your layer, eg in drawParameters. + * + * @see setNeedsDisplayAtScale: + */ +@property (nonatomic, assign, readonly) CGFloat contentsScaleForDisplay; + /** @name Touch handling */ @@ -361,38 +387,6 @@ */ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - -/** @name Observing node-related changes */ - - -/** - * Called just before the view is added to a window. - */ -- (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * Called after the view is removed from the window. - */ -- (void)didExitHierarchy ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers - * on the current node. - * - * @discussion Called by -recursivelyClearContents. Base class implements self.contents = nil, clearing any backing - * store, for asynchronous regeneration when needed. - */ -- (void)clearContents ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. - * - * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearFetchedData or - * selectively clear fetched data. - */ -- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER; - - /** @name Placeholders */ /** @@ -412,6 +406,7 @@ */ - (UIImage *)placeholderImage; + /** @name Description */ /** diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2d92c1883d..5fdcb4f706 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1722,6 +1722,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self clearFetchedData]; } +- (void)visibilityDidChange:(BOOL)isVisible +{ +} + /** * We currently only set interface state on nodes in table/collection views. For other nodes, if they are * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. @@ -1776,11 +1780,17 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // Entered or exited data loading state. if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { if (newState & ASInterfaceStateVisible) { - // Consider providing a -didBecomeVisible. + [self visibilityDidChange:YES]; } else { - // Consider providing a -didBecomeInvisible. + [self visibilityDidChange:NO]; } } + + [self interfaceStateDidChange:newState fromState:oldState]; +} + +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ } - (void)enterInterfaceState:(ASInterfaceState)interfaceState diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 281a5d0e06..7ef1fdf73e 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -11,14 +11,23 @@ @interface ASViewController : UIViewController +- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER; + @property (nonatomic, strong, readonly) ASDisplayNode *node; +/** + * @abstract Passthrough property to the the .interfaceState of the node. + * @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties. + * @see ASInterfaceState + */ +@property (nonatomic, readonly) ASInterfaceState interfaceState; + + // AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows // blocking as a view controller becomes visible to ensure no placeholders flash onscreen. // Refer to examples/SynchronousConcurrency, AsyncViewController.m @property (nonatomic, assign) BOOL neverShowPlaceholders; -- (instancetype)initWithNode:(ASDisplayNode *)node; /** * The constrained size used to measure the backing node. diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index ab2c6741e6..3b22a92937 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -65,4 +65,9 @@ return ASSizeRangeMake(viewSize, viewSize); } +- (ASInterfaceState)interfaceState +{ + return _node.interfaceState; +} + @end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 701be8940e..978032d91f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -57,6 +57,9 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) ASHierarchyState _hierarchyState; } +// The view class to use when creating a new display node instance. Defaults to _ASDisplayView. ++ (Class)viewClass; + // These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; - (void)exitInterfaceState:(ASInterfaceState)interfaceState; diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 57ec7f626d..88ca8858e3 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -130,7 +130,6 @@ AC3C4A5B1A11F47200143C57 /* Frameworks */, AC3C4A5C1A11F47200143C57 /* Resources */, A6902C454C7661D0D277AC62 /* Copy Pods Resources */, - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -201,21 +200,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From ff4f2bb270b73f865b2585b30e5a18da156ae3b5 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Dec 2015 22:19:53 -0800 Subject: [PATCH 04/19] Tweak ASCollectionViewFlowLayoutInspector.h file to make it Public as intended. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 2e0296d33b..f0aee074cd 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -128,7 +128,7 @@ 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; - 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; }; + 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; @@ -458,6 +458,7 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DE4A31861C2A747B000D4262 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1451,6 +1452,7 @@ B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */, 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, + DE4A31861C2A747B000D4262 /* ASCollectionViewFlowLayoutInspector.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, From 9944305cfdccda37cb3a984561507981e89d349e Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Dec 2015 23:36:29 -0800 Subject: [PATCH 05/19] Fix tests to avoid newly deprecated ASCollectionView init variant with asyncDataFetching argument. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ----------- AsyncDisplayKit/ASCollectionNode.h | 3 +- AsyncDisplayKit/ASCollectionNode.m | 9 ++++-- AsyncDisplayKit/ASCollectionView.h | 1 + AsyncDisplayKit/ASCollectionView.mm | 6 ++-- ...ASCollectionViewFlowLayoutInspectorTests.m | 28 +++++++++---------- 6 files changed, 26 insertions(+), 37 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f0aee074cd..2e2e112229 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1525,7 +1525,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1625,21 +1624,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 4f860cb646..8109c89769 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -14,7 +14,8 @@ */ @interface ASCollectionNode : ASDisplayNode -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @property (nonatomic, readonly) ASCollectionView *view; diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index eb0ddd3064..8c74d62cbe 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -10,7 +10,7 @@ #import "ASDisplayNode+Subclasses.h" @interface ASCollectionView (Internal) -- (ASCollectionView *)_initWithCollectionViewLayout:(UICollectionViewLayout *)layout; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @end @implementation ASCollectionNode @@ -24,7 +24,12 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] _initWithCollectionViewLayout:layout]; }]) { + return [self initWithFrame:CGRectZero collectionViewLayout:layout]; +} + +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout +{ + if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout]; }]) { return self; } return nil; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 8d715e3e68..5d93fdcd14 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -33,6 +33,7 @@ * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. */ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @property (nonatomic, weak) id asyncDataSource; @property (nonatomic, weak) id asyncDelegate; // must not be nil diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0aeefb2764..5d3d051bc4 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -193,10 +193,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { -// ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout]; -// collectionNode.frame = frame; -// return collectionNode.view; - return [self _initWithFrame:frame collectionViewLayout:layout]; + ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithFrame:frame collectionViewLayout:layout]; + return collectionNode.view; } // FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index 1230807f44..a9c5614e5f 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -100,7 +100,7 @@ layout.scrollDirection = UICollectionViewScrollDirectionVertical; CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; @@ -122,7 +122,7 @@ layout.scrollDirection = UICollectionViewScrollDirectionVertical; CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; @@ -146,7 +146,7 @@ layout.headerReferenceSize = CGSizeMake(125.0, 125.0); CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -167,7 +167,7 @@ layout.footerReferenceSize = CGSizeMake(125.0, 125.0); CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -190,7 +190,7 @@ layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; @@ -212,7 +212,7 @@ layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; @@ -236,7 +236,7 @@ layout.headerReferenceSize = CGSizeMake(125.0, 125.0); CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -257,7 +257,7 @@ layout.footerReferenceSize = CGSizeMake(125.0, 125.0); CGRect rect = CGRectMake(0, 0, 100.0, 100.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -274,7 +274,7 @@ InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init]; HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -291,7 +291,7 @@ - (void)testThatItRespondsWithTheDefaultNumberOfSections { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; XCTAssert(sections == 1, @"should return 1 by default"); @@ -304,7 +304,7 @@ { InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; @@ -321,7 +321,7 @@ InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init]; HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -338,7 +338,7 @@ HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.footerReferenceSize = CGSizeMake(125.0, 125.0); - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; @@ -354,7 +354,7 @@ InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init]; HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; From e35697d162f667047ae6b47835ac9e493262782a Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Wed, 23 Dec 2015 10:19:05 +0000 Subject: [PATCH 06/19] Greatly improve the internal logic of ASMapNode. Also fixes bug #971 --- AsyncDisplayKit/ASMapNode.h | 4 +- AsyncDisplayKit/ASMapNode.mm | 119 +++++++++++++++++++---------------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index acabf84152..bd2d23ad4b 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -12,7 +12,7 @@ @interface ASMapNode : ASImageNode /** - The current region of ASMapNode. This can be set at any time and ASMapNode will animate the change. + The current region of ASMapNode. This can be set at any time and ASMapNode will animate the change. This property may be set from a background thread before the node is loaded, and will automatically be applied to define the region of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise). */ @property (nonatomic, assign) MKCoordinateRegion region; @@ -22,7 +22,7 @@ @property (nonatomic, readonly) MKMapView *mapView; /** - Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. + Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded. */ @property (nonatomic, assign, getter=isLiveMap) BOOL liveMap; diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index 80a76fc82d..480360a1aa 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -26,6 +26,7 @@ @synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; @synthesize mapDelegate = _mapDelegate; @synthesize region = _region; +@synthesize liveMap = _liveMap; #pragma mark - Lifecycle - (instancetype)init @@ -37,9 +38,11 @@ self.clipsToBounds = YES; _needsMapReloadOnBoundsChange = YES; - + _liveMap = NO; _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; - _region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(43.432858, 13.183671), MKCoordinateSpanMake(0.2, 0.2)); + + //Default world-scale view + _region = MKCoordinateRegionForMapRect(MKMapRectWorld); _options = [[MKMapSnapshotOptions alloc] init]; _options.region = _region; @@ -50,26 +53,31 @@ - (void)didLoad { [super didLoad]; - if ([self wasLiveMapPreviously]) { + if (self.isLiveMap) { self.userInteractionEnabled = YES; [self addLiveMap]; } } +- (void)setLayerBacked:(BOOL)layerBacked +{ + ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); + [super setLayerBacked:layerBacked]; +} + - (void)fetchData { [super fetchData]; - if ([self wasLiveMapPreviously]) { + if (self.isLiveMap) { [self addLiveMap]; } else { - [self setUpSnapshotter]; [self takeSnapshot]; } } -- (void)clearFetchedData +- (void)clearContents { - [super clearFetchedData]; + [super clearContents]; if (self.isLiveMap) { [self removeLiveMap]; } @@ -79,17 +87,21 @@ - (BOOL)isLiveMap { - return (_mapView != nil); + ASDN::MutexLocker l(_propertyLock); + return _liveMap; } - (void)setLiveMap:(BOOL)liveMap { - liveMap ? [self addLiveMap] : [self removeLiveMap]; -} - -- (BOOL)wasLiveMapPreviously -{ - return CLLocationCoordinate2DIsValid(_centerCoordinateOfMap); + ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); + ASDN::MutexLocker l(_propertyLock); + if (liveMap == _liveMap) { + return; + } + _liveMap = liveMap; + if (self.nodeLoaded) { + liveMap ? [self addLiveMap] : [self removeLiveMap]; + } } - (BOOL)needsMapReloadOnBoundsChange @@ -127,63 +139,62 @@ - (void)takeSnapshot { - if (!_snapshotter.isLoading) { - [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - if (!error) { - UIImage *image = snapshot.image; - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointMake(0, 0)]; - - if (_annotations.count > 0 ) { - // Get a standard annotation view pin. Future implementations should use a custom annotation image property. - MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - UIImage *pinImage = pin.image; - for (idannotation in _annotations) + if (!_snapshotter) { + [self setUpSnapshotter]; + } + [_snapshotter cancel]; + [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { + if (!error) { + UIImage *image = snapshot.image; + CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); + + UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); + [image drawAtPoint:CGPointMake(0, 0)]; + + if (_annotations.count > 0 ) { + // Get a standard annotation view pin. Future implementations should use a custom annotation image property. + MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + UIImage *pinImage = pin.image; + for (idannotation in _annotations) + { + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) { - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) - { - CGPoint pinCenterOffset = pin.centerOffset; - point.x -= pin.bounds.size.width / 2.0; - point.y -= pin.bounds.size.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pin.bounds.size.width / 2.0; + point.y -= pin.bounds.size.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; } } - - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - self.image = finalImage; } - }]; - } + + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + self.image = finalImage; + } + }]; } - (void)setUpSnapshotter { - if (!_snapshotter) { - 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."); - _options.size = self.calculatedSize; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; - } + 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."); + _options.size = self.calculatedSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; } - (void)resetSnapshotter { - if (!_snapshotter.isLoading) { - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; - } + [_snapshotter cancel]; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; } #pragma mark - Actions - (void)addLiveMap { ASDisplayNodeAssertMainThread(); - if (!self.isLiveMap) { + if (!_mapView) { __weak ASMapNode *weakSelf = self; _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; _mapView.delegate = weakSelf.mapDelegate; @@ -194,8 +205,6 @@ if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) { [_mapView setCenterCoordinate:_centerCoordinateOfMap]; - } else { - _centerCoordinateOfMap = _options.region.center; } } } From 38d9ceb230abc1be1de9db5355d08d2cfc1e6556 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Dec 2015 12:52:38 -0800 Subject: [PATCH 07/19] Fix corrupted project file from merge. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 23 ++----------------- AsyncDisplayKit/ASViewController.m | 10 ++++++++ .../ASBasicImageDownloaderTests.m | 7 +++--- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ce1300c94f..a044ff4861 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -458,7 +458,7 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; - DE4A31861C2A747B000D4262 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1452,7 +1452,7 @@ B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */, 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, - DE4A31861C2A747B000D4262 /* ASCollectionViewFlowLayoutInspector.h in Headers */, + DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, 254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */, CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, @@ -1525,10 +1525,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, -<<<<<<< HEAD -======= - 5ADEA7587189397768F2B36C /* Embed Pods Frameworks */, ->>>>>>> master ); buildRules = ( ); @@ -1658,21 +1654,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 5ADEA7587189397768F2B36C /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 3b22a92937..519ac4b389 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -16,6 +16,16 @@ BOOL _ensureDisplayed; } +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + return [self initWithNode:nil]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + return [self initWithNode:nil]; +} + - (instancetype)initWithNode:(ASDisplayNode *)node { if (!(self = [super initWithNibName:nil bundle:nil])) { diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index e0d7e3c9fc..11271b23fd 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -1,5 +1,5 @@ // -// ASBasicImageDownloaderTests.m +// ASZBasicImageDownloaderTests.m // AsyncDisplayKit // // Created by Victor Mayorov on 10/06/15. @@ -10,11 +10,12 @@ #import -@interface ASBasicImageDownloaderTests : XCTestCase +// Z in the name to delay running until after the test instance is operating normally. +@interface ASZBasicImageDownloaderTests : XCTestCase @end -@implementation ASBasicImageDownloaderTests +@implementation ASZBasicImageDownloaderTests - (void)testAsynchronouslyDownloadTheSameURLTwice { From 928c440b4c8a5f7e8ff8d5a924931bfdd0b2a930 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Dec 2015 16:38:33 -0800 Subject: [PATCH 08/19] Several small optimizations, especially to _ASPendingState and other hot paths. --- AsyncDisplayKit/ASControlNode.m | 5 ++- AsyncDisplayKit/ASDisplayNode.mm | 8 ++-- AsyncDisplayKit/ASPagerNode.m | 11 +++--- AsyncDisplayKit/ASTextNode.mm | 4 +- .../Private/ASDisplayNode+AsyncDisplay.mm | 6 +-- .../Private/ASDisplayNodeInternal.h | 3 ++ AsyncDisplayKit/Private/_ASPendingState.m | 39 ++++++++++++------- 7 files changed, 48 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 8a5d89ed11..8278b9a2fc 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -76,7 +76,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v if (!(self = [super init])) return nil; - _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. _enabled = YES; // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. @@ -214,6 +213,10 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. if (!target) target = [NSNull null]; + + if (!_controlEventDispatchTable) { + _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. + } // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 5fdcb4f706..5540c7c882 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1470,9 +1470,9 @@ static NSInteger incrementIfFound(NSInteger i) { } // Helper method to summarize whether or not the node run through the display process -- (BOOL)_implementsDisplay +- (BOOL)__implementsDisplay { - return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES; + return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants; } - (void)_setupPlaceholderLayer @@ -1502,7 +1502,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). ASDisplayNode *node = [layer asyncdisplaykit_node]; - if (!layer.contents && [node _implementsDisplay]) { + if (!layer.contents && [node __implementsDisplay]) { // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. [layer displayIfNeeded]; @@ -2148,7 +2148,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, self.asyncLayer.displaySuspended = flag; - if ([self _implementsDisplay]) { + if ([self __implementsDisplay]) { if (flag) { [_supernode subnodeDisplayDidFinish:self]; } else { diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index fef60e2e2d..eead9a305b 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -20,13 +20,14 @@ - (instancetype)init { - _flowLayout = [[UICollectionViewFlowLayout alloc] init]; - _flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - _flowLayout.minimumInteritemSpacing = 0; - _flowLayout.minimumLineSpacing = 0; + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + flowLayout.minimumInteritemSpacing = 0; + flowLayout.minimumLineSpacing = 0; - self = [super initWithCollectionViewLayout:_flowLayout]; + self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { + _flowLayout = flowLayout; } return self; } diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 56490d18ba..3eb5c5ba59 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -97,6 +97,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation #pragma mark - NSObject +static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; + - (instancetype)init { if (self = [super init]) { @@ -120,7 +122,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation self.opaque = NO; self.backgroundColor = [UIColor clearColor]; - self.linkAttributeNames = @[ NSLinkAttributeName ]; + self.linkAttributeNames = DefaultLinkAttributeNames; // Accessibility self.isAccessibilityElement = YES; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 525d1b4bf8..b0cabb67f8 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -175,8 +175,6 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing { - id nodeClass = [self class]; - asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container."); @@ -235,7 +233,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, ASDN_DELAY_FOR_DISPLAY(); - UIImage *result = [nodeClass displayWithParameters:drawParameters isCancelled:isCancelledBlock]; + UIImage *result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock]; __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); return result; }; @@ -265,7 +263,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); } - [nodeClass drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; + [[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; if (isCancelledBlock()) { if (!rasterizing) { diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index e16eb99092..c4c9d2ab6f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -144,6 +144,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) // Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents - (void)__exitHierarchy; +// Helper method to summarize whether or not the node run through the display process +- (BOOL)__implementsDisplay; + // Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index a9f7088341..8d2c8f7665 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -135,19 +135,24 @@ @synthesize borderColor=borderColor; @synthesize asyncdisplaykit_asyncTransactionContainer=asyncTransactionContainer; + +static CGColorRef blackColorRef = NULL; +static UIColor *defaultTintColor = nil; + - (id)init { if (!(self = [super init])) return nil; - // Default UIKit color is an RGB color - static CGColorRef black; + static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + // Default UIKit color is an RGB color CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - black = CGColorCreate(colorSpace, (CGFloat[]){0,0,0,1} ); - CFRetain(black); + blackColorRef = CGColorCreate(colorSpace, (CGFloat[]){0,0,0,1} ); + CFRetain(blackColorRef); CGColorSpaceRelease(colorSpace); + defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0]; }); // Set defaults, these come from the defaults specified in CALayer and UIView @@ -156,7 +161,7 @@ frame = CGRectZero; bounds = CGRectZero; backgroundColor = nil; - tintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0]; + tintColor = defaultTintColor; contents = nil; isHidden = NO; needsDisplayOnBoundsChange = NO; @@ -172,14 +177,12 @@ transform = CATransform3DIdentity; sublayerTransform = CATransform3DIdentity; userInteractionEnabled = YES; - CFRetain(black); - shadowColor = black; + shadowColor = blackColorRef; shadowOpacity = 0.0; shadowOffset = CGSizeMake(0, -3); shadowRadius = 3; borderWidth = 0; - CFRetain(black); - borderColor = black; + borderColor = blackColorRef; isAccessibilityElement = NO; accessibilityLabel = nil; accessibilityHint = nil; @@ -376,7 +379,9 @@ return; } - CGColorRelease(shadowColor); + if (shadowColor != blackColorRef) { + CGColorRelease(shadowColor); + } shadowColor = color; CGColorRetain(shadowColor); @@ -413,7 +418,9 @@ return; } - CGColorRelease(borderColor); + if (borderColor != blackColorRef) { + CGColorRelease(borderColor); + } borderColor = color; CGColorRetain(borderColor); @@ -1002,8 +1009,14 @@ - (void)dealloc { CGColorRelease(backgroundColor); - CGColorRelease(shadowColor); - CGColorRelease(borderColor); + + if (shadowColor != blackColorRef) { + CGColorRelease(shadowColor); + } + + if (borderColor != blackColorRef) { + CGColorRelease(borderColor); + } } @end From ca57059322e817f31ef84f09a5d1a4e0fa729a65 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Dec 2015 20:01:52 -0800 Subject: [PATCH 09/19] New ASDelegateProxy class to unify logic for Table & Collection forwarding. Fix dealloc-during-animation crash. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++ .../contents.xcworkspacedata | 6 + AsyncDisplayKit/ASCollectionView.mm | 141 +++++------------ AsyncDisplayKit/ASTableView.mm | 142 +++++------------- AsyncDisplayKit/Details/ASDelegateProxy.h | 51 +++++++ AsyncDisplayKit/Details/ASDelegateProxy.m | 117 +++++++++++++++ .../ASBasicImageDownloaderTests.m | 19 +-- .../Sample.xcodeproj/project.pbxproj | 22 +-- .../contents.xcworkspacedata | 10 ++ .../ASCollectionView/Sample/AppDelegate.h | 2 + .../ASCollectionView/Sample/AppDelegate.m | 9 +- .../Sample/PresentingViewController.h | 13 ++ .../Sample/PresentingViewController.m | 30 ++++ .../ASCollectionView/Sample/ViewController.m | 41 ++++- 14 files changed, 382 insertions(+), 233 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASDelegateProxy.h create mode 100644 AsyncDisplayKit/Details/ASDelegateProxy.m create mode 100644 examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ASCollectionView/Sample/PresentingViewController.h create mode 100644 examples/ASCollectionView/Sample/PresentingViewController.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a044ff4861..136a89cb43 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -461,6 +461,10 @@ DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; }; + DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; }; + DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; }; + DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; @@ -754,6 +758,8 @@ D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; + DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; + DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1149,6 +1155,8 @@ 25B171EA1C12242700508A7A /* Data Controller */ = { isa = PBXGroup; children = ( + DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */, + DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */, 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, @@ -1264,6 +1272,7 @@ 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */, AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, + DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */, 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */, @@ -1432,6 +1441,7 @@ 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, + DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, @@ -1687,6 +1697,7 @@ 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, + DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, @@ -1816,6 +1827,7 @@ 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, + DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, diff --git a/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata b/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata index 574f0ec195..5f7400170b 100644 --- a/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata +++ b/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,12 @@ + + + + diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 5d3d051bc4..73e61653f5 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -8,6 +8,7 @@ #import "ASAssert.h" #import "ASBatchFetching.h" +#import "ASDelegateProxy.h" #import "ASCollectionView.h" #import "ASCollectionNode.h" #import "ASCollectionDataController.h" @@ -22,87 +23,6 @@ static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimation static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; -#pragma mark - -#pragma mark Proxying. - -/** - * ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods. - * - * Any selector included in this function *MUST* be implemented by ASCollectionView. - */ -static BOOL _isInterceptedSelector(SEL sel) -{ - return ( - // handled by ASCollectionView node<->cell machinery - sel == @selector(collectionView:cellForItemAtIndexPath:) || - sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) || - sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || - - // handled by ASRangeController - sel == @selector(numberOfSectionsInCollectionView:) || - sel == @selector(collectionView:numberOfItemsInSection:) || - - // used for ASRangeController visibility updates - sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || - sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || - - // used for batch fetching API - sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) - ); -} - - -/** - * Stand-in for UICollectionViewDataSource and UICollectionViewDelegate. Any method calls we intercept are routed to ASCollectionView; - * everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate. - */ -@interface _ASCollectionViewProxy : NSProxy -- (instancetype)initWithTarget:(id)target interceptor:(ASCollectionView *)interceptor; -@end - -@implementation _ASCollectionViewProxy { - id __weak _target; - ASCollectionView * __weak _interceptor; -} - -- (instancetype)initWithTarget:(id)target interceptor:(ASCollectionView *)interceptor -{ - // -[NSProxy init] is undefined - if (!self) { - return nil; - } - - ASDisplayNodeAssert(target, @"target must not be nil"); - ASDisplayNodeAssert(interceptor, @"interceptor must not be nil"); - - _target = target; - _interceptor = interceptor; - - return self; -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - - return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]); -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - - if (_isInterceptedSelector(aSelector)) { - return _interceptor; - } - - return [_target respondsToSelector:aSelector] ? _target : nil; -} - -@end - #pragma mark - #pragma mark ASCellNode<->UICollectionViewCell bridging. @@ -138,9 +58,9 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { - _ASCollectionViewProxy *_proxyDataSource; - _ASCollectionViewProxy *_proxyDelegate; +@interface ASCollectionView () { + ASCollectionViewProxy *_proxyDataSource; + ASCollectionViewProxy *_proxyDelegate; ASCollectionDataController *_dataController; ASRangeController *_rangeController; @@ -155,6 +75,7 @@ static BOOL _isInterceptedSelector(SEL sel) BOOL _collectionViewLayoutImplementsInsetSection; BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _queuedNodeSizeUpdate; + BOOL _isDeallocating; ASBatchContext *_batchContext; @@ -244,6 +165,12 @@ static BOOL _isInterceptedSelector(SEL sel) _layoutInspector = [self flowLayoutInspector]; } + _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; + super.delegate = (id)_proxyDelegate; + + _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; + super.dataSource = (id)_proxyDataSource; + _registeredSupplementaryKinds = [NSMutableSet set]; self.backgroundColor = [UIColor whiteColor]; @@ -255,10 +182,10 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)dealloc { - // Sometimes the UIKit classes can call back to their delegate even during deallocation. - // This bug might be iOS 7-specific. - super.delegate = nil; - super.dataSource = nil; + // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. + _isDeallocating = YES; + [self setAsyncDelegate:nil]; + [self setAsyncDataSource:nil]; } /** @@ -312,24 +239,35 @@ static BOOL _isInterceptedSelector(SEL sel) ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); } +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy +{ + if (proxy == _proxyDelegate) { + [self setAsyncDelegate:nil]; + } else if (proxy == _proxyDataSource) { + [self setAsyncDataSource:nil]; + } +} + - (void)setAsyncDataSource:(id)asyncDataSource { // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out - // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. + // super.dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes. + + super.dataSource = nil; if (asyncDataSource == nil) { - super.dataSource = nil; _asyncDataSource = nil; - _proxyDataSource = nil; + _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _asyncDataSourceImplementsConstrainedSizeForNode = NO; } else { _asyncDataSource = asyncDataSource; - _proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - super.dataSource = (id)_proxyDataSource; + _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0); } + + super.dataSource = (id)_proxyDataSource; } - (void)setAsyncDelegate:(id)asyncDelegate @@ -337,22 +275,25 @@ static BOOL _isInterceptedSelector(SEL sel) // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out - // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. + // super.delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes. + + // Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes + // in UIScrollViewAccessibility. + + super.delegate = nil; if (asyncDelegate == nil) { - // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes - // in UIScrollViewAccessibility. - super.delegate = nil; _asyncDelegate = nil; - _proxyDelegate = nil; + _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _asyncDelegateImplementsInsetSection = NO; } else { _asyncDelegate = asyncDelegate; - _proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - super.delegate = (id)_proxyDelegate; + _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); } + super.delegate = (id)_proxyDelegate; + [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index ddb6838037..8d799b5b4a 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -13,6 +13,7 @@ #import "ASBatchFetching.h" #import "ASChangeSetDataController.h" #import "ASCollectionViewLayoutController.h" +#import "ASDelegateProxy.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -24,87 +25,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) -#pragma mark - -#pragma mark Proxying. - -/** - * ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods. - * - * Any selector included in this function *MUST* be implemented by ASTableView. - */ -static BOOL _isInterceptedSelector(SEL sel) -{ - return ( - // handled by ASTableView node<->cell machinery - sel == @selector(tableView:cellForRowAtIndexPath:) || - sel == @selector(tableView:heightForRowAtIndexPath:) || - - // handled by ASRangeController - sel == @selector(numberOfSectionsInTableView:) || - sel == @selector(tableView:numberOfRowsInSection:) || - - // used for ASRangeController visibility updates - sel == @selector(tableView:willDisplayCell:forRowAtIndexPath:) || - sel == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || - - // used for batch fetching API - sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) - ); -} - - -/** - * Stand-in for UITableViewDataSource and UITableViewDelegate. Any method calls we intercept are routed to ASTableView; - * everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate. - */ -@interface _ASTableViewProxy : NSProxy -- (instancetype)initWithTarget:(id)target interceptor:(ASTableView *)interceptor; -@end - -@implementation _ASTableViewProxy { - id __weak _target; - ASTableView * __weak _interceptor; -} - -- (instancetype)initWithTarget:(id)target interceptor:(ASTableView *)interceptor -{ - // -[NSProxy init] is undefined - if (!self) { - return nil; - } - - ASDisplayNodeAssert(target, @"target must not be nil"); - ASDisplayNodeAssert(interceptor, @"interceptor must not be nil"); - - _target = target; - _interceptor = interceptor; - - return self; -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - - return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]); -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - - if (_isInterceptedSelector(aSelector)) { - return _interceptor; - } - - return [_target respondsToSelector:aSelector] ? _target : nil; -} - -@end - - #pragma mark - #pragma mark ASCellNode<->UITableViewCell bridging. @@ -156,13 +76,12 @@ static BOOL _isInterceptedSelector(SEL sel) @end - #pragma mark - #pragma mark ASTableView -@interface ASTableView () { - _ASTableViewProxy *_proxyDataSource; - _ASTableViewProxy *_proxyDelegate; +@interface ASTableView () { + ASTableViewProxy *_proxyDataSource; + ASTableViewProxy *_proxyDelegate; ASFlowLayoutController *_layoutController; @@ -180,6 +99,7 @@ static BOOL _isInterceptedSelector(SEL sel) CGFloat _nodesConstrainedWidth; BOOL _ignoreNodesConstrainedWidthChange; BOOL _queuedNodeHeightUpdate; + BOOL _isDeallocating; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -224,6 +144,12 @@ static BOOL _isInterceptedSelector(SEL sel) // If the initial size is 0, expect a size change very soon which is part of the initial configuration // and should not trigger a relayout. _ignoreNodesConstrainedWidthChange = (_nodesConstrainedWidth == 0); + + _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; + super.delegate = (id)_proxyDelegate; + + _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; + super.dataSource = (id)_proxyDataSource; [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; } @@ -265,9 +191,9 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)dealloc { // Sometimes the UIKit classes can call back to their delegate even during deallocation. - // This bug might be iOS 7-specific. - super.delegate = nil; - super.dataSource = nil; + _isDeallocating = YES; + [self setAsyncDelegate:nil]; + [self setAsyncDataSource:nil]; } #pragma mark - @@ -290,17 +216,19 @@ static BOOL _isInterceptedSelector(SEL sel) // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out - // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. - + // super.dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes. + + super.dataSource = nil; + if (asyncDataSource == nil) { - super.dataSource = nil; _asyncDataSource = nil; - _proxyDataSource = nil; + _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; } else { _asyncDataSource = asyncDataSource; - _proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - super.dataSource = (id)_proxyDataSource; + _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; } + + super.dataSource = (id)_proxyDataSource; } - (void)setAsyncDelegate:(id)asyncDelegate @@ -308,18 +236,30 @@ static BOOL _isInterceptedSelector(SEL sel) // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out - // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. + // super.delegate in this case because calls to ASTableViewProxy will start failing and cause crashes. + + // Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes + // in UIScrollViewAccessibility. + super.delegate = nil; + if (asyncDelegate == nil) { - // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes - // in UIScrollViewAccessibility. - super.delegate = nil; _asyncDelegate = nil; - _proxyDelegate = nil; + _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; } else { _asyncDelegate = asyncDelegate; - _proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:asyncDelegate interceptor:self]; - super.delegate = (id)_proxyDelegate; + _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; + } + + super.delegate = (id)_proxyDelegate; +} + +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy +{ + if (proxy == _proxyDelegate) { + [self setAsyncDelegate:nil]; + } else if (proxy == _proxyDataSource) { + [self setAsyncDataSource:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.h b/AsyncDisplayKit/Details/ASDelegateProxy.h new file mode 100644 index 0000000000..ca5ae5da01 --- /dev/null +++ b/AsyncDisplayKit/Details/ASDelegateProxy.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class ASDelegateProxy; +@protocol ASDelegateProxyInterceptor +@required +// Called if the target object is discovered to be nil if it had been non-nil at init time. +// This happens if the object is deallocated, because the proxy must maintain a weak reference to avoid cycles. +// Though the target object may become nil, the interceptor must not; it is assumed the interceptor owns the proxy. +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy; +@end + +/** + * Stand-in for delegates like UITableView or UICollectionView's delegate / dataSource. + * Any selectors flagged by "interceptsSelector" are routed to the interceptor object and are not delivered to the target. + * Everything else leaves AsyncDisplayKit safely and arrives at the original target object. + */ + +@interface ASDelegateProxy : NSProxy + +- (instancetype)initWithTarget:(id)target interceptor:(id )interceptor; + +// This method must be overridden by a subclass. +- (BOOL)interceptsSelector:(SEL)selector; + +@end + +/** + * ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods. + * + * Any selector included in this function *MUST* be implemented by ASTableView. + */ + +@interface ASTableViewProxy : ASDelegateProxy +@end + +/** + * ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods. + * + * Any selector included in this function *MUST* be implemented by ASCollectionView. + */ + +@interface ASCollectionViewProxy : ASDelegateProxy +@end diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m new file mode 100644 index 0000000000..baaff1e62c --- /dev/null +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -0,0 +1,117 @@ +/* Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASDelegateProxy.h" +#import "ASTableView.h" +#import "ASCollectionView.h" +#import "ASAssert.h" + +@implementation ASTableViewProxy + +- (BOOL)interceptsSelector:(SEL)selector +{ + return ( + // handled by ASTableView node<->cell machinery + selector == @selector(tableView:cellForRowAtIndexPath:) || + selector == @selector(tableView:heightForRowAtIndexPath:) || + + // handled by ASRangeController + selector == @selector(numberOfSectionsInTableView:) || + selector == @selector(tableView:numberOfRowsInSection:) || + + // used for ASRangeController visibility updates + selector == @selector(tableView:willDisplayCell:forRowAtIndexPath:) || + selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || + + // used for batch fetching API + selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + ); +} + +@end + +@implementation ASCollectionViewProxy + +- (BOOL)interceptsSelector:(SEL)selector +{ + return ( + // handled by ASCollectionView node<->cell machinery + selector == @selector(collectionView:cellForItemAtIndexPath:) || + selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) || + selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || + + // handled by ASRangeController + selector == @selector(numberOfSectionsInCollectionView:) || + selector == @selector(collectionView:numberOfItemsInSection:) || + + // used for ASRangeController visibility updates + selector == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || + selector == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || + + // used for batch fetching API + selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + ); +} + +@end + +@implementation ASDelegateProxy { + id __weak _target; + id __weak _interceptor; +} + +- (instancetype)initWithTarget:(id )target interceptor:(id )interceptor +{ + // -[NSProxy init] is undefined + if (!self) { + return nil; + } + + ASDisplayNodeAssert(interceptor, @"interceptor must not be nil"); + + _target = target ? : [NSNull null]; + _interceptor = interceptor; + + return self; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + + if ([self interceptsSelector:aSelector]) { + return YES; + } else { + // Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization). + return [_target respondsToSelector:aSelector]; + } +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); + + if ([self interceptsSelector:aSelector]) { + return _interceptor; + } else { + if (_target) { + return [_target respondsToSelector:aSelector] ? _target : nil; + } else { + [_interceptor proxyTargetHasDeallocated:self]; + return nil; + } + } +} + +- (BOOL)interceptsSelector:(SEL)selector +{ + ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses."); + return NO; +} + +@end diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index 11271b23fd..9def4240a1 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -11,45 +11,38 @@ #import // Z in the name to delay running until after the test instance is operating normally. -@interface ASZBasicImageDownloaderTests : XCTestCase +@interface ASBasicImageDownloaderTests : XCTestCase @end -@implementation ASZBasicImageDownloaderTests +@implementation ASBasicImageDownloaderTests - (void)testAsynchronouslyDownloadTheSameURLTwice { ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader]; NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; - - dispatch_group_t group = dispatch_group_create(); - + __block BOOL firstDone = NO; - dispatch_group_enter(group); [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgressBlock:nil completion:^(CGImageRef image, NSError *error) { firstDone = YES; - dispatch_group_leave(group); }]; __block BOOL secondDone = NO; - dispatch_group_enter(group); [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgressBlock:nil completion:^(CGImageRef image, NSError *error) { secondDone = YES; - dispatch_group_leave(group); }]; - - XCTAssert(0 == dispatch_group_wait(group, dispatch_time(0, 10 * 1000000000)), @"URL loading takes too long"); - - XCTAssert(firstDone && secondDone, @"Not all handlers has been called"); + + sleep(3); + XCTAssert(firstDone && secondDone, @"Not all ASBasicImageDownloader completion handlers have been called after 3 seconds"); } @end diff --git a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj index 56aef5f7f6..3cdc925105 100644 --- a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; }; + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -34,6 +35,8 @@ AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = ""; }; + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresentingViewController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -82,6 +85,8 @@ AC3C4A661A11F47200143C57 /* AppDelegate.m */, AC3C4A681A11F47200143C57 /* ViewController.h */, AC3C4A691A11F47200143C57 /* ViewController.m */, + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */, + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */, AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, AC3C4A611A11F47200143C57 /* Supporting Files */, 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, @@ -125,7 +130,6 @@ AC3C4A5B1A11F47200143C57 /* Frameworks */, AC3C4A5C1A11F47200143C57 /* Resources */, A6902C454C7661D0D277AC62 /* Copy Pods Resources */, - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -196,21 +200,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -236,6 +225,7 @@ 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */, AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, AC3C4A641A11F47200143C57 /* main.m in Sources */, ); diff --git a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/ASCollectionView/Sample/AppDelegate.h b/examples/ASCollectionView/Sample/AppDelegate.h index 2aa29369b4..80b7fb3d50 100644 --- a/examples/ASCollectionView/Sample/AppDelegate.h +++ b/examples/ASCollectionView/Sample/AppDelegate.h @@ -11,6 +11,8 @@ #import +#define SIMULATE_WEB_RESPONSE 0 + @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; diff --git a/examples/ASCollectionView/Sample/AppDelegate.m b/examples/ASCollectionView/Sample/AppDelegate.m index a979170a63..19ceedd960 100644 --- a/examples/ASCollectionView/Sample/AppDelegate.m +++ b/examples/ASCollectionView/Sample/AppDelegate.m @@ -11,6 +11,7 @@ #import "AppDelegate.h" +#import "PresentingViewController.h" #import "ViewController.h" @implementation AppDelegate @@ -31,10 +32,14 @@ - (void)pushNewViewControllerAnimated:(BOOL)animated { UINavigationController *navController = (UINavigationController *)self.window.rootViewController; - + +#if SIMULATE_WEB_RESPONSE + UIViewController *viewController = [[PresentingViewController alloc] init]; +#else UIViewController *viewController = [[ViewController alloc] init]; viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Another Copy" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; - +#endif + [navController pushViewController:viewController animated:animated]; } diff --git a/examples/ASCollectionView/Sample/PresentingViewController.h b/examples/ASCollectionView/Sample/PresentingViewController.h new file mode 100644 index 0000000000..bd2308fab3 --- /dev/null +++ b/examples/ASCollectionView/Sample/PresentingViewController.h @@ -0,0 +1,13 @@ +// +// PresentingViewController.h +// Sample +// +// Created by Tom King on 12/23/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@interface PresentingViewController : UIViewController + +@end diff --git a/examples/ASCollectionView/Sample/PresentingViewController.m b/examples/ASCollectionView/Sample/PresentingViewController.m new file mode 100644 index 0000000000..49c65e6906 --- /dev/null +++ b/examples/ASCollectionView/Sample/PresentingViewController.m @@ -0,0 +1,30 @@ +// +// PresentingViewController.m +// Sample +// +// Created by Tom King on 12/23/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "PresentingViewController.h" +#import "ViewController.h" + +@interface PresentingViewController () + +@end + +@implementation PresentingViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Details" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; +} + +- (void)pushNewViewController +{ + ViewController *controller = [[ViewController alloc] init]; + [self.navigationController pushViewController:controller animated:true]; +} + +@end diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index a9d3e12a7b..00aea567dc 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -18,6 +18,7 @@ @interface ViewController () { ASCollectionView *_collectionView; + NSArray *_data; } @end @@ -37,7 +38,7 @@ layout.headerReferenceSize = CGSizeMake(50.0, 50.0); layout.footerReferenceSize = CGSizeMake(50.0, 50.0); - _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES]; + _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.asyncDataSource = self; _collectionView.asyncDelegate = self; _collectionView.backgroundColor = [UIColor whiteColor]; @@ -45,8 +46,10 @@ [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; +#if !SIMULATE_WEB_RESPONSE self.navigationItem.leftItemsSupplementBackButton = YES; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; +#endif return self; } @@ -56,6 +59,31 @@ [super viewDidLoad]; [self.view addSubview:_collectionView]; + +#if SIMULATE_WEB_RESPONSE + __weak typeof(self) weakSelf = self; + void(^mockWebService)() = ^{ + NSLog(@"ViewController \"got data from a web service\""); + ViewController *strongSelf = weakSelf; + if (strongSelf != nil) + { + NSLog(@"ViewController is not nil"); + strongSelf->_data = [[NSArray alloc] init]; + [strongSelf->_collectionView performBatchUpdates:^{ + [strongSelf->_collectionView insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; + } completion:nil]; + NSLog(@"ViewController finished updating collectionView"); + } + else { + NSLog(@"ViewController is nil - won't update collectionView"); + } + }; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self.navigationController popViewControllerAnimated:YES]; + }); +#endif } - (void)viewWillLayoutSubviews @@ -101,7 +129,11 @@ - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { +#if SIMULATE_WEB_RESPONSE + return _data == nil ? 0 : 100; +#else return 100; +#endif } - (void)collectionViewLockDataSource:(ASCollectionView *)collectionView @@ -125,4 +157,11 @@ return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0); } +#if SIMULATE_WEB_RESPONSE +-(void)dealloc +{ + NSLog(@"ViewController is deallocing"); +} +#endif + @end From 8b3f3351abad83735aac515ce9e79f5eb477fa74 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Dec 2015 21:53:42 -0800 Subject: [PATCH 10/19] Logging for test failures that are only occurring on build server with iOS 8.1 simulator... --- AsyncDisplayKit/ASCollectionView.mm | 6 ++++++ AsyncDisplayKit/Details/ASDelegateProxy.m | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 73e61653f5..c9f52c1466 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -184,8 +184,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. _isDeallocating = YES; + NSLog(@"before dealloc - delegate - %@, datasource - %@, proxyDelegate - %@, proxyDatasource - %@", self.asyncDelegate, self.asyncDataSource, _proxyDelegate, _proxyDataSource); [self setAsyncDelegate:nil]; [self setAsyncDataSource:nil]; + NSLog(@"after dealloc - delegate - %@, datasource - %@, proxyDelegate - %@, proxyDatasource - %@", self.asyncDelegate, self.asyncDataSource, _proxyDelegate, _proxyDataSource); } /** @@ -267,6 +269,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0); } + NSLog(@"setAsyncDataSource - %@, proxy - %@, deallocating - %d", asyncDataSource, _proxyDataSource, _isDeallocating); + super.dataSource = (id)_proxyDataSource; } @@ -292,6 +296,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); } + NSLog(@"setAsyncDelegate - %@, proxy - %@, deallocating - %d", asyncDelegate, _proxyDelegate, _isDeallocating); + super.delegate = (id)_proxyDelegate; [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index baaff1e62c..cd64c4827c 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -82,10 +82,8 @@ - (BOOL)respondsToSelector:(SEL)aSelector { - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - if ([self interceptsSelector:aSelector]) { - return YES; + return (_interceptor != nil); } else { // Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization). return [_target respondsToSelector:aSelector]; @@ -94,8 +92,6 @@ - (id)forwardingTargetForSelector:(SEL)aSelector { - ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil"); - if ([self interceptsSelector:aSelector]) { return _interceptor; } else { From 15826b58e424a83b3af348ee082f598d7f934a4c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Dec 2015 22:24:17 -0800 Subject: [PATCH 11/19] Remove extra logging for build server debugging. --- AsyncDisplayKit/ASCollectionView.mm | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index c9f52c1466..c044919bd0 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -184,10 +184,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. _isDeallocating = YES; - NSLog(@"before dealloc - delegate - %@, datasource - %@, proxyDelegate - %@, proxyDatasource - %@", self.asyncDelegate, self.asyncDataSource, _proxyDelegate, _proxyDataSource); [self setAsyncDelegate:nil]; [self setAsyncDataSource:nil]; - NSLog(@"after dealloc - delegate - %@, datasource - %@, proxyDelegate - %@, proxyDatasource - %@", self.asyncDelegate, self.asyncDataSource, _proxyDelegate, _proxyDataSource); } /** @@ -269,8 +267,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0); } - NSLog(@"setAsyncDataSource - %@, proxy - %@, deallocating - %d", asyncDataSource, _proxyDataSource, _isDeallocating); - super.dataSource = (id)_proxyDataSource; } @@ -295,9 +291,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); } - - NSLog(@"setAsyncDelegate - %@, proxy - %@, deallocating - %d", asyncDelegate, _proxyDelegate, _isDeallocating); - + super.delegate = (id)_proxyDelegate; [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; From 27c151095b3a6d2454742eed62783eb17d125812 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 17:06:57 -0800 Subject: [PATCH 12/19] [ASPagerNode] New API tweaks. Support setting delegate + dataSource on ASCollectionNode and ASTableNode without triggering view creation. --- AsyncDisplayKit/ASCollectionNode.h | 3 + AsyncDisplayKit/ASCollectionNode.m | 63 +++++++++++++++++++- AsyncDisplayKit/ASCollectionView.h | 4 ++ AsyncDisplayKit/ASPagerNode.h | 20 ++++++- AsyncDisplayKit/ASPagerNode.m | 49 +++++++++++---- AsyncDisplayKit/ASTableNode.h | 4 ++ AsyncDisplayKit/ASTableNode.m | 59 +++++++++++++++++- AsyncDisplayKit/ASTableView.h | 4 ++ AsyncDisplayKit/Details/ASDelegateProxy.h | 4 ++ AsyncDisplayKit/Details/ASDelegateProxy.m | 14 +++++ AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm | 45 +++++++------- 11 files changed, 230 insertions(+), 39 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 8109c89769..8dcdb793ae 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -17,6 +17,9 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; + @property (nonatomic, readonly) ASCollectionView *view; /** diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 8c74d62cbe..7cf49fbcb6 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -9,7 +9,19 @@ #import "ASCollectionNode.h" #import "ASDisplayNode+Subclasses.h" -@interface ASCollectionView (Internal) +@interface _ASCollectionPendingState : NSObject +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; +@end + +@implementation _ASCollectionPendingState +@end + +@interface ASCollectionNode () +@property (nonatomic) _ASCollectionPendingState *pendingState; +@end + +@interface ASCollectionView () - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @end @@ -29,12 +41,59 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { - if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout]; }]) { + ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ + return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout]; + }; + + if (self = [super initWithViewBlock:collectionViewBlock]) { return self; } return nil; } +- (void)didLoad +{ + [super didLoad]; + + if (_pendingState) { + _ASCollectionPendingState *pendingState = _pendingState; + self.pendingState = nil; + + ASCollectionView *view = self.view; + view.asyncDelegate = pendingState.delegate; + view.asyncDataSource = pendingState.dataSource; + } +} + +- (_ASCollectionPendingState *)pendingState +{ + if (!_pendingState && ![self isNodeLoaded]) { + self.pendingState = [[_ASCollectionPendingState alloc] init]; + } + ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASCollectionNode should not have a pendingState once it is loaded"); + return _pendingState; +} + +- (void)setDelegate:(id )delegate +{ + if ([self pendingState]) { + _pendingState.delegate = delegate; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.asyncDelegate = delegate; + } +} + +- (void)setDataSource:(id )dataSource +{ + if ([self pendingState]) { + _pendingState.dataSource = dataSource; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.asyncDataSource = dataSource; + } +} + - (ASCollectionView *)view { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 5d93fdcd14..b0072e64cf 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -286,6 +286,8 @@ /** * This is a node-based UICollectionViewDataSource. */ +@protocol ASCollectionDataSource +@end @protocol ASCollectionViewDataSource /** @@ -347,6 +349,8 @@ /** * This is a node-based UICollectionViewDelegate. */ +@protocol ASCollectionDelegate +@end @protocol ASCollectionViewDelegate @optional diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 36355c91dd..f2f69b61f3 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -12,7 +12,21 @@ @interface ASPagerNode : ASCollectionNode -@property (weak, nonatomic) id dataSource; +// Configures a default horizontal, paging flow layout with 0 inter-item spacing. +- (instancetype)init; + +// Initializer with custom-configured flow layout properties. +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; + +// The underlying ASCollectionView object. +- (ASCollectionView *)collectionView; + +// Delegate is optional, and uses the same protocol as ASCollectionNode. +// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... +@property (weak, nonatomic) id delegate; + +// Data Source is required, and uses a different protocol from ASCollectionNode. +@property (weak, nonatomic) id dataSource; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; @@ -20,8 +34,10 @@ @protocol ASPagerNodeDataSource +// This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; +// This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index eead9a305b..dbaaa97300 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -7,11 +7,14 @@ // #import "ASPagerNode.h" +#import "ASDelegateProxy.h" #import @interface ASPagerNode () { UICollectionViewFlowLayout *_flowLayout; + ASPagerNodeProxy *_proxy; + id _pagerDataSource; } @end @@ -25,6 +28,11 @@ flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumLineSpacing = 0; + return [self initWithFlowLayout:flowLayout]; +} + +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout +{ self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; @@ -32,17 +40,38 @@ return self; } +- (ASCollectionView *)collectionView +{ + return self.view; +} + +- (void)setDataSource:(id )pagerDataSource +{ + if (pagerDataSource != _pagerDataSource) { + _pagerDataSource = pagerDataSource; + _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + super.dataSource = _proxy; + } +} + +- (id )dataSource +{ + return _pagerDataSource; +} + - (void)didLoad { [super didLoad]; - self.view.asyncDataSource = self; - self.view.asyncDelegate = self; + ASCollectionView *cv = self.view; + cv.asyncDataSource = self; + cv.asyncDelegate = self; - self.view.pagingEnabled = YES; - self.view.allowsSelection = NO; - self.view.showsVerticalScrollIndicator = NO; - self.view.showsHorizontalScrollIndicator = NO; + cv.pagingEnabled = YES; + cv.allowsSelection = NO; + cv.showsVerticalScrollIndicator = NO; + cv.showsHorizontalScrollIndicator = NO; + cv.scrollsToTop = NO; ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; @@ -62,14 +91,14 @@ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); - return [self.dataSource pagerNode:self nodeAtIndex:indexPath.item]; + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); - return [self.dataSource numberOfPagesInPagerNode:self]; + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [_pagerDataSource numberOfPagesInPagerNode:self]; } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index 026414be15..d859df677d 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -18,4 +18,8 @@ @property (nonatomic, readonly) ASTableView *view; +// These properties can be set without triggering the view to be created, so it's fine to set them in -init. +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; + @end diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 11287a8389..136c6843aa 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -8,18 +8,71 @@ #import "ASTableNode.h" +@interface _ASTablePendingState : NSObject +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; +@end + +@implementation _ASTablePendingState +@end + +@interface ASTableNode () +@property (nonatomic) _ASTablePendingState *pendingState; +@end + @implementation ASTableNode - (instancetype)initWithStyle:(UITableViewStyle)style { - if (self = [super initWithViewBlock:^UIView *{ - return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; - }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; }]) { return self; } return nil; } +- (void)didLoad +{ + [super didLoad]; + + if (_pendingState) { + _ASTablePendingState *pendingState = _pendingState; + self.pendingState = nil; + + ASTableView *view = self.view; + view.asyncDelegate = pendingState.delegate; + view.asyncDataSource = pendingState.dataSource; + } +} + +- (_ASTablePendingState *)pendingState +{ + if (!_pendingState && ![self isNodeLoaded]) { + self.pendingState = [[_ASTablePendingState alloc] init]; + } + ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded"); + return _pendingState; +} + +- (void)setDelegate:(id )delegate +{ + if ([self pendingState]) { + _pendingState.delegate = delegate; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.asyncDelegate = delegate; + } +} + +- (void)setDataSource:(id )dataSource +{ + if ([self pendingState]) { + _pendingState.dataSource = dataSource; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.asyncDataSource = dataSource; + } +} + - (ASTableView *)view { return (ASTableView *)[super view]; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 67c4b852c1..f6746874b9 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -280,6 +280,8 @@ /** * This is a node-based UITableViewDataSource. */ +@protocol ASTableDataSource +@end @protocol ASTableViewDataSource /** @@ -324,6 +326,8 @@ * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. */ +@protocol ASTableDelegate +@end @protocol ASTableViewDelegate @optional diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.h b/AsyncDisplayKit/Details/ASDelegateProxy.h index ca5ae5da01..850328a8ec 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.h +++ b/AsyncDisplayKit/Details/ASDelegateProxy.h @@ -49,3 +49,7 @@ @interface ASCollectionViewProxy : ASDelegateProxy @end + +@interface ASPagerNodeProxy : ASDelegateProxy +@end + diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index cd64c4827c..8054b2b018 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -60,6 +60,20 @@ @end +@implementation ASPagerNodeProxy + +- (BOOL)interceptsSelector:(SEL)selector +{ + return ( + // handled by ASPagerNodeDataSource node<->cell machinery + selector == @selector(collectionView:nodeForItemAtIndexPath:) || + selector == @selector(collectionView:numberOfItemsInSection:) || + selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) + ); +} + +@end + @implementation ASDelegateProxy { id __weak _target; id __weak _interceptor; diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index f346017316..ae9575593f 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -39,33 +39,34 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - CGSize size = { - constrainedSize.max.width, - constrainedSize.max.height - }; + CGSize maxConstrainedSize = CGSizeMake(constrainedSize.max.width, constrainedSize.max.height); + + NSArray *children = self.children; + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:self.children.count]; - for (id child in self.children) { - CGSize autoMaxSize = { - constrainedSize.max.width - child.layoutPosition.x, - constrainedSize.max.height - child.layoutPosition.y - }; - ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.sizeRange) - ? ASSizeRangeMake({0, 0}, autoMaxSize) - : ASRelativeSizeRangeResolve(child.sizeRange, size); + for (id child in children) { + CGPoint layoutPosition = child.layoutPosition; + CGSize autoMaxSize = CGSizeMake(maxConstrainedSize.width - layoutPosition.x, + maxConstrainedSize.height - layoutPosition.y); + + ASRelativeSizeRange childSizeRange = child.sizeRange; + BOOL childIsUnconstrained = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, childSizeRange); + ASSizeRange childConstraint = childIsUnconstrained ? ASSizeRangeMake({0, 0}, autoMaxSize) + : ASRelativeSizeRangeResolve(childSizeRange, maxConstrainedSize); + ASLayout *sublayout = [child measureWithSizeRange:childConstraint]; - sublayout.position = child.layoutPosition; + sublayout.position = layoutPosition; [sublayouts addObject:sublayout]; } - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } + CGSize size = CGSizeMake(constrainedSize.min.width, constrainedSize.min.height); - size.height = constrainedSize.min.height; for (ASLayout *sublayout in sublayouts) { - size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); + CGPoint sublayoutPosition = sublayout.position; + CGSize sublayoutSize = sublayout.size; + + size.width = MAX(size.width, sublayoutPosition.x + sublayoutSize.width); + size.height = MAX(size.height, sublayoutPosition.y + sublayoutSize.height); } return [ASLayout layoutWithLayoutableObject:self @@ -75,12 +76,12 @@ - (void)setChild:(id)child forIdentifier:(NSString *)identifier { - ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren"); + ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports setChildren"); } - (id)childForIdentifier:(NSString *)identifier { - ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports children"); + ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports children"); return nil; } From c1640c7f59d8d1cec6a18f6ec04bd87563a4078c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 18:02:52 -0800 Subject: [PATCH 13/19] Implement getter methods for new table / collection delegate / dataSource. Make ASTableView node-backed. --- AsyncDisplayKit/ASCollectionNode.m | 18 ++++++++++++++++++ AsyncDisplayKit/ASTableNode.m | 24 +++++++++++++++++++++++- AsyncDisplayKit/ASTableView.mm | 23 ++++++++++++----------- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 7cf49fbcb6..315c3626b9 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -84,6 +84,15 @@ } } +- (id )delegate +{ + if ([self pendingState]) { + return _pendingState.delegate; + } else { + return self.view.asyncDelegate; + } +} + - (void)setDataSource:(id )dataSource { if ([self pendingState]) { @@ -94,6 +103,15 @@ } } +- (id )dataSource +{ + if ([self pendingState]) { + return _pendingState.dataSource; + } else { + return self.view.asyncDataSource; + } +} + - (ASCollectionView *)view { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 136c6843aa..2d9bbc33bb 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -20,11 +20,15 @@ @property (nonatomic) _ASTablePendingState *pendingState; @end +@interface ASTableView () +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style; +@end + @implementation ASTableNode - (instancetype)initWithStyle:(UITableViewStyle)style { - if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero style:style]; }]) { return self; } return nil; @@ -63,6 +67,15 @@ } } +- (id )delegate +{ + if ([self pendingState]) { + return _pendingState.delegate; + } else { + return self.view.asyncDelegate; + } +} + - (void)setDataSource:(id )dataSource { if ([self pendingState]) { @@ -73,6 +86,15 @@ } } +- (id )dataSource +{ + if ([self pendingState]) { + return _pendingState.dataSource; + } else { + return self.view.asyncDataSource; + } +} + - (ASTableView *)view { return (ASTableView *)[super view]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 8d799b5b4a..162a0fa1b2 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -8,6 +8,7 @@ #import "ASTableView.h" #import "ASTableViewInternal.h" +#import "ASTableNode.h" #import "ASAssert.h" #import "ASBatchFetching.h" @@ -159,33 +160,33 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return [self initWithFrame:frame style:style asyncDataFetching:NO]; } +// FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled { return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled]; } - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:style]; + tableNode.frame = frame; + return tableNode.view; +} + +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style { if (!(self = [super initWithFrame:frame style:style])) return nil; - - // FIXME: asyncDataFetching is currently unreliable for some use cases. - // https://github.com/facebook/AsyncDisplayKit/issues/385 - asyncDataFetchingEnabled = NO; - [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:asyncDataFetchingEnabled]; + [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - if (!(self = [super initWithCoder:aDecoder])) - return nil; - - [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; - - return self; + NSLog(@"Warning: AsyncDisplayKit is not designed to be used with Interface Builder. Table properties set in IB will be lost."); + return [self initWithFrame:CGRectZero style:UITableViewStylePlain]; } - (void)dealloc From 7ece41ff645b9ee9e132b06cbc2993f3f8f84b5b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 21:41:12 -0800 Subject: [PATCH 14/19] Delegate definition tweaks for Table and Collection; ensure Table tests run with ARC enabled. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.h | 18 +++++++--------- AsyncDisplayKit/ASPagerNode.h | 21 ++++++++++--------- AsyncDisplayKit/ASPagerNode.m | 10 +++++++-- AsyncDisplayKit/ASTableNode.h | 3 ++- AsyncDisplayKit/ASTableNode.m | 18 +++++++++++++--- AsyncDisplayKit/ASTableView.h | 21 +++++++------------ AsyncDisplayKit/ASTableView.mm | 20 +++++++++++++----- .../ASCollectionViewFlowLayoutInspector.h | 4 ++-- .../ASCollectionViewFlowLayoutInspector.m | 2 +- AsyncDisplayKitTests/ASTableViewTests.m | 14 +++++++------ 11 files changed, 79 insertions(+), 54 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 136a89cb43..02830223a8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -242,7 +242,7 @@ 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; - 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; }; 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index b0072e64cf..f7cd8da049 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -15,8 +15,8 @@ #import @class ASCellNode; -@protocol ASCollectionViewDataSource; -@protocol ASCollectionViewDelegate; +@protocol ASCollectionDataSource; +@protocol ASCollectionDelegate; @protocol ASCollectionViewLayoutInspecting; /** @@ -35,8 +35,8 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; -@property (nonatomic, weak) id asyncDataSource; -@property (nonatomic, weak) id asyncDelegate; // must not be nil +@property (nonatomic, weak) id asyncDelegate; +@property (nonatomic, weak) id asyncDataSource; /** * Tuning parameters for a range type. @@ -286,9 +286,8 @@ /** * This is a node-based UICollectionViewDataSource. */ -@protocol ASCollectionDataSource -@end -@protocol ASCollectionViewDataSource +#define ASCollectionViewDataSource ASCollectionDataSource +@protocol ASCollectionDataSource /** * Similar to -collectionView:cellForItemAtIndexPath:. @@ -349,9 +348,8 @@ /** * This is a node-based UICollectionViewDelegate. */ -@protocol ASCollectionDelegate -@end -@protocol ASCollectionViewDelegate +#define ASCollectionViewDelegate ASCollectionDelegate +@protocol ASCollectionDelegate @optional diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index f2f69b61f3..325eacea53 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -8,7 +8,17 @@ #import -@protocol ASPagerNodeDataSource; +@class ASPagerNode; + +@protocol ASPagerNodeDataSource + +// This method replaces -collectionView:numberOfItemsInSection: +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; + +// This method replaces -collectionView:nodeForItemAtIndexPath: +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +@end @interface ASPagerNode : ASCollectionNode @@ -32,12 +42,3 @@ @end -@protocol ASPagerNodeDataSource - -// This method replaces -collectionView:numberOfItemsInSection: -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - -// This method replaces -collectionView:nodeForItemAtIndexPath: -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - -@end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index dbaaa97300..d4537d0a68 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -11,7 +11,7 @@ #import -@interface ASPagerNode () { +@interface ASPagerNode () { UICollectionViewFlowLayout *_flowLayout; ASPagerNodeProxy *_proxy; id _pagerDataSource; @@ -20,6 +20,7 @@ @end @implementation ASPagerNode +@dynamic delegate; - (instancetype)init { @@ -50,10 +51,15 @@ if (pagerDataSource != _pagerDataSource) { _pagerDataSource = pagerDataSource; _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; - super.dataSource = _proxy; + super.dataSource = (id )_proxy; } } +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy +{ + [self setDataSource:nil]; +} + - (id )dataSource { return _pagerDataSource; diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index d859df677d..9ab36470a6 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -14,7 +14,8 @@ */ @interface ASTableNode : ASDisplayNode -- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; +- (instancetype)init; // UITableViewStylePlain +- (instancetype)initWithStyle:(UITableViewStyle)style; @property (nonatomic, readonly) ASTableView *view; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 2d9bbc33bb..f45f742d1e 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -21,19 +21,31 @@ @end @interface ASTableView () -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style; +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; @end @implementation ASTableNode -- (instancetype)initWithStyle:(UITableViewStyle)style +- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass { - if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero style:style]; }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero + style:style + dataControllerClass:dataControllerClass]; }]) { return self; } return nil; } +- (instancetype)initWithStyle:(UITableViewStyle)style +{ + return [self _initWithStyle:style dataControllerClass:nil]; +} + +- (instancetype)init +{ + return [self _initWithStyle:UITableViewStylePlain dataControllerClass:nil]; +} + - (void)didLoad { [super didLoad]; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index f6746874b9..ae43552d03 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -7,17 +7,14 @@ */ #import - #import #import #import #import - @class ASCellNode; -@protocol ASTableViewDataSource; -@protocol ASTableViewDelegate; - +@protocol ASTableDataSource; +@protocol ASTableDelegate; /** * Node-based table view. @@ -27,8 +24,8 @@ */ @interface ASTableView : UITableView -@property (nonatomic, weak) id asyncDelegate; // must not be nil -@property (nonatomic, weak) id asyncDataSource; +@property (nonatomic, weak) id asyncDelegate; +@property (nonatomic, weak) id asyncDataSource; /** * Initializer. @@ -280,9 +277,8 @@ /** * This is a node-based UITableViewDataSource. */ -@protocol ASTableDataSource -@end -@protocol ASTableViewDataSource +#define ASTableViewDataSource ASTableDataSource +@protocol ASTableDataSource /** * Similar to -tableView:cellForRowAtIndexPath:. @@ -326,9 +322,8 @@ * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. */ -@protocol ASTableDelegate -@end -@protocol ASTableViewDelegate +#define ASTableViewDelegate ASTableDelegate +@protocol ASTableDelegate @optional diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 162a0fa1b2..a5e65178d7 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -21,6 +21,8 @@ #import "ASLayoutController.h" #import "ASRangeController.h" +#import + static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; //#define LOG(...) NSLog(__VA_ARGS__) @@ -80,6 +82,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark ASTableView +@interface ASTableNode () +- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; +@end + @interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -168,17 +174,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled { - ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:style]; - tableNode.frame = frame; - return tableNode.view; +// ASTableNode *tableNode = [[ASTableNode alloc] _initWithStyle:style dataControllerClass:dataControllerClass]; +// tableNode.frame = frame; +// return tableNode.view; + return [self _initWithFrame:frame style:style dataControllerClass:dataControllerClass]; } -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass { if (!(self = [super initWithFrame:frame style:style])) return nil; - [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; + if (!dataControllerClass) { + dataControllerClass = [self.class dataControllerClass]; + } + [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:NO]; return self; } diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 2890cab842..b2ee2ae8a8 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -11,7 +11,7 @@ #import @class ASCollectionView; -@protocol ASCollectionViewDelegate; +@protocol ASCollectionDelegate; @protocol ASCollectionViewLayoutInspecting @@ -42,7 +42,7 @@ * * @discussion A great time to update perform selector caches! */ -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(id)delegate; @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index f6221489cf..f98afbb97a 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -36,7 +36,7 @@ return self; } -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(id)delegate; { if (delegate == nil) { _delegateImplementsReferenceSizeForHeader = NO; diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index b4e137bcd7..a382b6be8c 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -52,7 +52,6 @@ if (_willDeallocBlock) { _willDeallocBlock(self); } - [super dealloc]; } @end @@ -78,7 +77,6 @@ if (_willDeallocBlock) { _willDeallocBlock(self); } - [super dealloc]; } @end @@ -130,6 +128,7 @@ @implementation ASTableViewTests +// TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; @@ -148,11 +147,11 @@ tableView.asyncDataSource = delegate; tableView.asyncDelegate = delegate; - - [delegate release]; + +// [delegate release]; XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate); - XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); +// XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView); } @@ -399,7 +398,10 @@ style:UITableViewStylePlain asyncDataFetching:YES]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + tableView.asyncDelegate = dataSource; tableView.asyncDataSource = dataSource; From a0e4484ef7438d94d9a02013a919d5f91d6bb41d Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 22:27:52 -0800 Subject: [PATCH 15/19] Declare ASPagerNode dataSource property as @dynamic so that it can be a different type than ASCollectionNode. --- AsyncDisplayKit/ASPagerNode.h | 5 +---- AsyncDisplayKit/ASPagerNode.m | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 325eacea53..2377d1c1be 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -9,15 +9,12 @@ #import @class ASPagerNode; - -@protocol ASPagerNodeDataSource - +@protocol ASPagerNodeDataSource // This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; // This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - @end @interface ASPagerNode : ASCollectionNode diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index d4537d0a68..31e7bbada8 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -20,7 +20,7 @@ @end @implementation ASPagerNode -@dynamic delegate; +@dynamic delegate, dataSource; - (instancetype)init { From f902b4bdc73185fecd6f7090ff098583e62812d5 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 22:47:54 -0800 Subject: [PATCH 16/19] Replace property declaration with method overrides for -dataSource. --- AsyncDisplayKit/ASPagerNode.h | 5 +++-- AsyncDisplayKit/ASPagerNode.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 2377d1c1be..746a805514 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -9,7 +9,7 @@ #import @class ASPagerNode; -@protocol ASPagerNodeDataSource +@protocol ASPagerNodeDataSource // This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; @@ -33,7 +33,8 @@ @property (weak, nonatomic) id delegate; // Data Source is required, and uses a different protocol from ASCollectionNode. -@property (weak, nonatomic) id dataSource; +- (void)setDataSource:(id )dataSource; +- (id )dataSource; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 31e7bbada8..d4537d0a68 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -20,7 +20,7 @@ @end @implementation ASPagerNode -@dynamic delegate, dataSource; +@dynamic delegate; - (instancetype)init { From 4ca97e2f4dd597d0e23747bd78ab75327b927213 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 23:13:50 -0800 Subject: [PATCH 17/19] Optimize string handling for CALayer gravity & UIView content mode. Finally fix protocol rename. --- AsyncDisplayKit/ASTableView.h | 9 ++++--- .../Private/_ASCoreAnimationExtras.mm | 27 ++++++------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index ae43552d03..410b073f3a 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -277,7 +277,6 @@ /** * This is a node-based UITableViewDataSource. */ -#define ASTableViewDataSource ASTableDataSource @protocol ASTableDataSource /** @@ -315,6 +314,8 @@ @end +@protocol ASTableViewDataSource +@end /** * This is a node-based UITableViewDelegate. @@ -322,7 +323,6 @@ * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. */ -#define ASTableViewDelegate ASTableDelegate @protocol ASTableDelegate @optional @@ -357,4 +357,7 @@ */ - (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView; -@end \ No newline at end of file +@end + +@protocol ASTableViewDelegate ; +@end diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index 4ff3c52e4f..a6205e1d58 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -7,7 +7,7 @@ */ #import "_ASCoreAnimationExtras.h" - +#import "ASEqualityHelpers.h" #import "ASAssert.h" extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) @@ -87,7 +87,8 @@ static const struct _UIContentModeStringLUTEntry UIContentModeDescriptionLUT[] = {UIViewContentModeBottomRight, @"bottomRight"}, }; -NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) { +NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) +{ for (int i=0; i< ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { if (UIContentModeDescriptionLUT[i].contentMode == contentMode) { return UIContentModeDescriptionLUT[i].string; @@ -96,16 +97,10 @@ NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) return [NSString stringWithFormat:@"%d", (int)contentMode]; } -UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string) { - // If you passed one of the constants (this is just an optimization to avoid string comparison) +UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string) +{ for (int i=0; i < ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { - if (UIContentModeDescriptionLUT[i].string == string) { - return UIContentModeDescriptionLUT[i].contentMode; - } - } - // If you passed something isEqualToString: to one of the constants - for (int i=0; i < ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { - if ([UIContentModeDescriptionLUT[i].string isEqualToString:string]) { + if (ASObjectIsEqual(UIContentModeDescriptionLUT[i].string, string)) { return UIContentModeDescriptionLUT[i].contentMode; } } @@ -126,18 +121,12 @@ NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMod UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) { - // If you passed one of the constants (this is just an optimization to avoid string comparison) for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { - if (UIContentModeCAGravityLUT[i].string == contentsGravity) { - return UIContentModeCAGravityLUT[i].contentMode; - } - } - // If you passed something isEqualToString: to one of the constants - for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { - if ([UIContentModeCAGravityLUT[i].string isEqualToString:contentsGravity]) { + if (ASObjectIsEqual(UIContentModeCAGravityLUT[i].string, contentsGravity)) { return UIContentModeCAGravityLUT[i].contentMode; } } + ASDisplayNodeCAssert(contentsGravity, @"Encountered an unknown contentsGravity \"%@\". Is this a new version of iOS?", contentsGravity); ASDisplayNodeCAssert(!contentsGravity, @"You passed nil to ASDisplayNodeUIContentModeFromCAContentsGravity. We're falling back to resize, but this is probably a bug."); // If asserts disabled, fall back to this From 99fbc97bdaecd2f99009b9734ba0f8f2b665632b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 25 Dec 2015 01:36:32 -0800 Subject: [PATCH 18/19] Optimizations for ASTextNode handling of renderer allocation and deallocation. Optimizations for ASDisplayNode handling of bridged property "contentMode", especially for layer-backed nodes. --- AsyncDisplayKit/ASTextNode.mm | 21 ++++++- .../Private/ASDisplayNode+UIViewBridge.mm | 16 +++-- AsyncDisplayKit/Private/ASInternalHelpers.h | 1 + AsyncDisplayKit/Private/ASInternalHelpers.mm | 12 ++++ .../Private/_ASCoreAnimationExtras.mm | 24 +++++++- AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 58 ++++++++++++++----- 6 files changed, 107 insertions(+), 25 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3eb5c5ba59..fdcfc26b19 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -62,6 +62,15 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (void)dealloc { CGColorRelease(_backgroundColor); + + // Destruction of the layout managers/containers/text storage is quite + // expensive, and can take some time, so we dispatch onto a bg queue to + // actually dealloc. + __block ASTextKitRenderer *renderer = _renderer; + ASPerformBlockOnBackgroundThread(^{ + renderer = nil; + }); + _renderer = nil; } @end @@ -157,6 +166,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (_shadowColor != NULL) { CGColorRelease(_shadowColor); } + + [self _invalidateRenderer]; if (_longPressGestureRecognizer) { _longPressGestureRecognizer.delegate = nil; @@ -189,6 +200,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return [[self _renderer] size]; } +// FIXME: Re-evaluate if it is still the right decision to clear the renderer at this stage. +// This code was written before TextKit and when 512MB devices were still the overwhelming majority. - (void)displayDidFinish { [super displayDidFinish]; @@ -263,16 +276,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_invalidateRenderer { ASDN::MutexLocker l(_rendererLock); + if (_renderer) { // Destruction of the layout managers/containers/text storage is quite // expensive, and can take some time, so we dispatch onto a bg queue to // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + ASPerformBlockOnBackgroundThread(^{ renderer = nil; }); + _renderer = nil; } - _renderer = nil; } - (void)_invalidateRendererIfNeeded @@ -320,7 +334,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Modifying User Text -- (void)setAttributedString:(NSAttributedString *)attributedString { +- (void)setAttributedString:(NSAttributedString *)attributedString +{ if (ASObjectIsEqual(attributedString, _attributedString)) { return; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 2239cc038a..d14c042c0b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -446,19 +446,27 @@ { _bridge_prologue; if (__loaded) { - return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); + if (_flags.layerBacked) { + return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); + } else { + return _view.contentMode; + } } else { return self.pendingViewState.contentMode; } } -- (void)setContentMode:(UIViewContentMode)mode +- (void)setContentMode:(UIViewContentMode)contentMode { _bridge_prologue; if (__loaded) { - _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(mode); + if (_flags.layerBacked) { + _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); + } else { + _view.contentMode = contentMode; + } } else { - self.pendingViewState.contentMode = mode; + self.pendingViewState.contentMode = contentMode; } } diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 4d1e2ddca8..1f173bd951 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -18,6 +18,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); void ASPerformBlockOnMainThread(void (^block)()); +void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT CGFloat ASScreenScale(); diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 89a0e81709..71ac09d21f 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -57,6 +57,18 @@ void ASPerformBlockOnMainThread(void (^block)()) } } +void ASPerformBlockOnBackgroundThread(void (^block)()) +{ + if ([NSThread isMainThread]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + block(); + }); + } else { + block(); + } +} + + CGFloat ASScreenScale() { static CGFloat _scale; diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index a6205e1d58..a0c3b14ece 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -119,11 +119,31 @@ NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMod return nil; } +#define ContentModeCacheSize 10 UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) { - for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { + static int currentCacheIndex = 0; + static NSMutableArray *cachedStrings = [NSMutableArray arrayWithCapacity:ContentModeCacheSize]; + static UIViewContentMode cachedModes[ContentModeCacheSize] = {}; + + NSInteger foundCacheIndex = [cachedStrings indexOfObjectIdenticalTo:contentsGravity]; + if (foundCacheIndex != NSNotFound && foundCacheIndex < ContentModeCacheSize) { + return cachedModes[foundCacheIndex]; + } + + for (int i = 0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { if (ASObjectIsEqual(UIContentModeCAGravityLUT[i].string, contentsGravity)) { - return UIContentModeCAGravityLUT[i].contentMode; + UIViewContentMode foundContentMode = UIContentModeCAGravityLUT[i].contentMode; + + if (currentCacheIndex < ContentModeCacheSize) { + // Cache the input value. This is almost always a different pointer than in our LUT and will frequently + // be the same value for an overwhelming majority of inputs. + [cachedStrings addObject:contentsGravity]; + cachedModes[currentCacheIndex] = foundContentMode; + currentCacheIndex++; + } + + return foundContentMode; } } diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index c00d006159..7630c105db 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -32,7 +32,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() @implementation ASTextKitRenderer { CGSize _calculatedSize; + BOOL _sizeIsCalculated; } +@synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater; #pragma mark - Initialization @@ -42,30 +44,50 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() if (self = [super init]) { _constrainedSize = constrainedSize; _attributes = attributes; + _sizeIsCalculated = NO; + } + return self; +} +- (ASTextKitShadower *)shadower +{ + if (!_shadower) { + ASTextKitAttributes attributes = _attributes; _shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset shadowColor:attributes.shadowColor shadowOpacity:attributes.shadowOpacity shadowRadius:attributes.shadowRadius]; + } + return _shadower; +} +- (ASTextKitTailTruncater *)truncater +{ + if (!_truncater) { + ASTextKitAttributes attributes = _attributes; // We must inset the constrained size by the size of the shadower. - CGSize shadowConstrainedSize = [_shadower insetSizeWithConstrainedSize:_constrainedSize]; + CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; + _truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context] + truncationAttributedString:attributes.truncationAttributedString + avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet() + constrainedSize:shadowConstrainedSize]; + } + return _truncater; +} +- (ASTextKitContext *)context +{ + if (!_context) { + ASTextKitAttributes attributes = _attributes; + CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString lineBreakMode:attributes.lineBreakMode maximumNumberOfLines:attributes.maximumNumberOfLines exclusionPaths:attributes.exclusionPaths constrainedSize:shadowConstrainedSize layoutManagerFactory:attributes.layoutManagerFactory]; - - _truncater = [[ASTextKitTailTruncater alloc] initWithContext:_context - truncationAttributedString:attributes.truncationAttributedString - avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet() - constrainedSize:shadowConstrainedSize]; - - [self _calculateSize]; } - return self; + return _context; } #pragma mark - Sizing @@ -74,14 +96,14 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() { // Force glyph generation and layout, which may not have happened yet (and isn't triggered by // -usedRectForTextContainer:). - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [layoutManager ensureLayoutForTextContainer:textContainer]; }]; CGRect constrainedRect = {CGPointZero, _constrainedSize}; __block CGRect boundingRect; - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { boundingRect = [layoutManager usedRectForTextContainer:textContainer]; }]; @@ -94,6 +116,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (CGSize)size { + if (!_sizeIsCalculated) { + [self _calculateSize]; + _sizeIsCalculated = YES; + } return _calculatedSize; } @@ -104,13 +130,13 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // We add an assertion so we can track the rare conditions where a graphics context is not present ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); - CGRect shadowInsetBounds = [_shadower insetRectWithConstrainedRect:bounds]; + CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; CGContextSaveGState(context); - [_shadower setShadowInContext:context]; + [[self shadower] setShadowInContext:context]; UIGraphicsPushContext(context); - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; @@ -125,7 +151,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (NSUInteger)lineCount { __block NSUInteger lineCount = 0; - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) { [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; } @@ -135,7 +161,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (std::vector)visibleRanges { - return _truncater.visibleRanges; + return [self truncater].visibleRanges; } @end From af74f4a1c26fe65b36fb81c7861ee720f0f06051 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 25 Dec 2015 14:01:18 -0800 Subject: [PATCH 19/19] Fix the AsyncDisplayKit tests under Xcode 7! Workaround for unfixed Apple bug with @dynamic on CALayer. --- AsyncDisplayKit-Prefix.gcda | Bin 656 -> 656 bytes AsyncDisplayKit.xcodeproj/project.pbxproj | 32 ++++++++------ .../contents.xcworkspacedata | 6 --- AsyncDisplayKit/ASDisplayNode.mm | 17 +++++--- .../_ASAsyncTransactionContainer.m | 40 ++++++++++++++++-- 5 files changed, 69 insertions(+), 26 deletions(-) diff --git a/AsyncDisplayKit-Prefix.gcda b/AsyncDisplayKit-Prefix.gcda index 72e86999c9372666b6b78fd16af20e83deaa4c29..7564c6c3f5969754bf6ba6879ed2be0be65a12ff 100644 GIT binary patch literal 656 zcmYdHNlw=?GB9a6bMz1c0|TQ1kN|-u_8Tg)Ksr9k&)Cp0IJhJ+Iom0*I5j6TFEt>) zII|=(KQA@KC$TcWw8T6)KewQ?B-J&@+sGKI7F`Wc$_Oe2(G*;enjBnGnUfmiZ3wa; zvnn+xF)uw8Xs2UAK@N~&6ao=IEck5i2Yw(OALV0cngn%(TYiyeNop=`a|66B9D^Zt z0rfhjqy&SE69y7YK-{({PDlVq1N|3VVjLfDXjYb*Tmm#R)FM89`ma9&q;>0v!!;ay%$Vh<7){e4r2fK<0}9305FBTf4Rg6gCu_Z!KLru z&b3b~9nkD~{$I*R-v!N}O>=~_e-{$2`N1N}wt_I-wb0p)mlH2LQ&MC^=cMLgM?ozDpLD z$>FfceKxi&bzeEiI`t+8#sJQYC?Ioa>aE7XHh^{1S+{jit?A@M7DhYGsv-ZJr$};S z%zpq9lrD-|dkL>W6h)r5Ree diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 02830223a8..1cc2d937cf 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -461,10 +461,10 @@ DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; - DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; }; - DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; }; - DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; }; - DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; }; + DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; + DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; + DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; + DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; @@ -758,8 +758,8 @@ D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; - DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; - DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; + DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; + DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1155,8 +1155,8 @@ 25B171EA1C12242700508A7A /* Data Controller */ = { isa = PBXGroup; children = ( - DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */, - DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */, + DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */, + DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */, 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, @@ -1272,7 +1272,7 @@ 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */, AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, - DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */, + DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */, @@ -1441,7 +1441,7 @@ 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, - DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */, + DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, @@ -1697,7 +1697,7 @@ 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, - DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */, + DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, @@ -1827,7 +1827,7 @@ 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, - DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */, + DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, @@ -1934,6 +1934,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1954,6 +1955,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -2041,6 +2043,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DSTROOT = /tmp/AsyncDisplayKit.dst; GCC_INPUT_FILETYPE = automatic; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -2059,6 +2062,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DSTROOT = /tmp/AsyncDisplayKit.dst; GCC_INPUT_FILETYPE = automatic; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -2081,6 +2085,7 @@ "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2108,6 +2113,7 @@ "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2136,6 +2142,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2168,6 +2175,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "$(SRCROOT)/AsyncDisplayKit-iOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata b/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata index 5f7400170b..574f0ec195 100644 --- a/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata +++ b/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata @@ -1,12 +1,6 @@ - - - - diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 5540c7c882..52f38cda9a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2317,7 +2317,6 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @implementation UIView (ASDisplayNodeInternal) -@dynamic asyncdisplaykit_node; - (void)setAsyncdisplaykit_node:(ASDisplayNode *)node { @@ -2326,16 +2325,24 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (ASDisplayNode *)asyncdisplaykit_node { - ASDisplayNode *node = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return node; + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); } @end @implementation CALayer (ASDisplayNodeInternal) -@dynamic asyncdisplaykit_node; -@end +- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. +} + +- (ASDisplayNode *)asyncdisplaykit_node +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); +} + +@end @implementation UIView (AsyncDisplayKit) diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m index cbf26a6de3..fd2d02a6b2 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionContainer.m @@ -10,19 +10,53 @@ #import "_ASAsyncTransaction.h" #import "_ASAsyncTransactionGroup.h" +#import + +static const char *ASDisplayNodeAssociatedTransactionsKey = "ASAssociatedTransactions"; +static const char *ASDisplayNodeAssociatedCurrentTransactionKey = "ASAssociatedCurrentTransaction"; @implementation CALayer (ASAsyncTransactionContainerTransactions) -@dynamic asyncdisplaykit_asyncLayerTransactions; -@dynamic asyncdisplaykit_currentAsyncLayerTransaction; + +- (_ASAsyncTransaction *)asyncdisplaykit_currentAsyncLayerTransaction +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedCurrentTransactionKey); +} + +- (void)asyncdisplaykit_setCurrentAsyncLayerTransaction:(_ASAsyncTransaction *)transaction +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedCurrentTransactionKey, transaction, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSHashTable *)asyncdisplaykit_asyncLayerTransactions +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedTransactionsKey); +} + +- (void)asyncdisplaykit_setAsyncLayerTransactions:(NSHashTable *)transactions +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedTransactionsKey, transactions, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} // No-ops in the base class. Mostly exposed for testing. - (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction {} - (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction {} @end +static const char *ASAsyncTransactionIsContainerKey = "ASTransactionIsContainer"; + @implementation CALayer (ASDisplayNodeAsyncTransactionContainer) -@dynamic asyncdisplaykit_asyncTransactionContainer; +- (BOOL)asyncdisplaykit_isAsyncTransactionContainer +{ + CFBooleanRef isContainerBool = (__bridge CFBooleanRef)objc_getAssociatedObject(self, ASAsyncTransactionIsContainerKey); + BOOL isContainer = (isContainerBool == kCFBooleanTrue); + return isContainer; +} + +- (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)isContainer +{ + objc_setAssociatedObject(self, ASAsyncTransactionIsContainerKey, (id)(isContainer ? kCFBooleanTrue : kCFBooleanFalse), OBJC_ASSOCIATION_ASSIGN); +} - (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState {