From 4b82cc4051c5c26d25a7f4eb451e50b202c05482 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 2 Sep 2015 15:01:22 +0300 Subject: [PATCH 01/39] While adjusting position of a node, use getters and setters. Those methods already check thread affinity and layer readiness. --- AsyncDisplayKit/ASDisplayNode.mm | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 59d0b7833a..13e74b9d7f 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -601,25 +601,20 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. [self measureWithSizeRange:oldConstrainedSize]; - CGSize oldSize = self.bounds.size; + CGRect oldBounds = self.bounds; + CGSize oldSize = oldBounds.size; CGSize newSize = _layout.size; if (! CGSizeEqualToSize(oldSize, newSize)) { - CGRect bounds = self.bounds; - bounds.size = newSize; - self.bounds = bounds; + self.bounds = (CGRect){ oldBounds.origin, newSize }; // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); - CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); - CGPoint oldPosition = (useLayer ? _layer.position : self.position); - + CGPoint anchorPoint = self.anchorPoint; + CGPoint oldPosition = self.position; CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - CGPoint newPosition = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - - useLayer ? _layer.position = newPosition : self.position = newPosition; + self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); } } } From 93e53bbd87077c1a058dd80ae587f5bff2b36047 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 2 Sep 2015 15:21:19 +0300 Subject: [PATCH 02/39] Use getter and setter of other properties in ASDisplayNode's frame setter. --- .../Private/ASDisplayNode+UIViewBridge.mm | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index ef013a97c2..4490654258 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -143,22 +143,12 @@ ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); #endif - BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); + CGPoint origin = self.bounds.origin; + CGPoint anchorPoint = self.anchorPoint; - CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin); - CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); - - CGRect bounds = (CGRect){ origin, rect.size }; - CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); - - if (useLayer) { - _layer.bounds = bounds; - _layer.position = position; - } else { - self.bounds = bounds; - self.position = position; - } + self.bounds = (CGRect){ origin, rect.size }; + self.position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, + rect.origin.y + rect.size.height * anchorPoint.y); } - (void)setNeedsDisplay From a1ffa499cb6a865d7e557c112a7c231768b923fb Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 21 Sep 2015 00:15:03 -0700 Subject: [PATCH 03/39] Assert against the user trying to set isLayerBacked on ASEditableTextNode --- AsyncDisplayKit/ASEditableTextNode.h | 2 +- AsyncDisplayKit/ASEditableTextNode.mm | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index e343b4b420..62b9f51251 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -11,7 +11,7 @@ @protocol ASEditableTextNodeDelegate; -/// @abstract ASEditableTextNode implements a node that supports text editing. +/// @abstract ASEditableTextNode implements a view-backed node that supports text editing. @interface ASEditableTextNode : ASDisplayNode // @abstract The text node's delegate, which must conform to the protocol. diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index bd04062b29..c5b90f397d 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -188,6 +188,11 @@ _placeholderTextKitComponents.textView.opaque = opaque; } +- (void)setLayerBacked:(BOOL)layerBacked { + ASDisplayNodeAssert(!layerBacked, @"Cannot set isLayerBacked to YES on ASEditableTextNode – all instances are view-backed."); + [super setLayerBacked:layerBacked]; +} + #pragma mark - Configuration @synthesize delegate = _delegate; From e9f463da73fb0b43a275817f15e0168961c378a9 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 21 Sep 2015 00:16:02 -0700 Subject: [PATCH 04/39] Use the right name --- AsyncDisplayKit/ASEditableTextNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index c5b90f397d..d253debbc7 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -189,7 +189,7 @@ } - (void)setLayerBacked:(BOOL)layerBacked { - ASDisplayNodeAssert(!layerBacked, @"Cannot set isLayerBacked to YES on ASEditableTextNode – all instances are view-backed."); + ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – all instances are view-backed."); [super setLayerBacked:layerBacked]; } From 5faea03ce8a8c7c3804456dbfb974ae87e600852 Mon Sep 17 00:00:00 2001 From: Kyle Levin Date: Tue, 22 Sep 2015 10:23:46 -0400 Subject: [PATCH 05/39] added ASViewController to the dynamic framework target to fix carthage support --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3f6adff9cd..c2fe465643 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -140,6 +140,8 @@ 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; + 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; settings = {ASSET_TAGS = (); }; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1207,6 +1209,7 @@ 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, + 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, @@ -1534,6 +1537,7 @@ B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */, From d09888f9c235b502143042c9b017a214cabb1fa3 Mon Sep 17 00:00:00 2001 From: Steven Ramkumar Date: Tue, 22 Sep 2015 11:16:55 -0700 Subject: [PATCH 06/39] Add a indexPathForNode: to ASTableView --- AsyncDisplayKit/ASTableView.h | 9 ++++++++ AsyncDisplayKit/ASTableView.mm | 5 +++++ AsyncDisplayKit/Details/ASDataController.h | 2 ++ AsyncDisplayKit/Details/ASDataController.mm | 16 ++++++++++++++ AsyncDisplayKitTests/ASTableViewTests.m | 23 +++++++++++++++++++++ 5 files changed, 55 insertions(+) diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index ed3ecd6212..c562f83f2f 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -227,6 +227,15 @@ */ - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath; +/** + * Similar to -indexPathForCell:. + * + * @param cellNode a cellNode part of the table view + * + * @returns an indexPath for this cellNode + */ +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; + /** * Similar to -visibleCells. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index b7ebe9c2f1..47ed7778ac 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -345,6 +345,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { return [_dataController nodeAtIndexPath:indexPath]; } +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode +{ + return [_dataController indexPathForNode:cellNode]; +} + - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleRows]; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 68e586daa8..589d210f9d 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -173,6 +173,8 @@ typedef NSUInteger ASDataControllerAnimationOptions; - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath; +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; + - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths; - (NSArray *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index b0e7a94bb5..3b80baecd0 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -632,6 +632,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return _completedNodes[indexPath.section][indexPath.row]; } +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; +{ + ASDisplayNodeAssertMainThread(); + + // Loop through each section to look for the cellNode + for (NSUInteger i = 0; i < [_completedNodes count]; i++) { + NSArray *sectionNodes = _completedNodes[i]; + NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; + if (cellIndex != NSNotFound) { + return [NSIndexPath indexPathForRow:cellIndex inSection:i]; + } + } + + return nil; +} + - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index bdba88cf19..a0d2a413b7 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -428,4 +428,27 @@ }]; } +- (void)testIndexPathForNode +{ + CGSize tableViewSize = CGSizeMake(100, 500); + ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + style:UITableViewStylePlain + asyncDataFetching:YES]; + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableView.asyncDelegate = dataSource; + tableView.asyncDataSource = dataSource; + + [tableView reloadDataWithCompletion:^{ + for (NSUInteger i = 0; i < NumberOfSections; i++) { + for (NSUInteger j = 0; j < NumberOfRowsPerSection; j++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i]; + ASCellNode *cellNode = [tableView nodeForRowAtIndexPath:indexPath]; + NSIndexPath *reportedIndexPath = [tableView indexPathForNode:cellNode]; + XCTAssertEqual(indexPath.row, reportedIndexPath.row); + } + } + }]; +} + @end From 20c74229a9bef73c5450047f837c7b7bf597c572 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 22 Sep 2015 13:16:59 -0700 Subject: [PATCH 07/39] Updates from PR --- AsyncDisplayKit/ASEditableTextNode.h | 5 ++++- AsyncDisplayKit/ASEditableTextNode.mm | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 62b9f51251..0581cfe638 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -11,7 +11,10 @@ @protocol ASEditableTextNodeDelegate; -/// @abstract ASEditableTextNode implements a view-backed node that supports text editing. +/** + @abstract Implements a node that supports text editing. + @discussion Does not support layer backing. + */ @interface ASEditableTextNode : ASDisplayNode // @abstract The text node's delegate, which must conform to the protocol. diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index d253debbc7..5e8156a86a 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -188,8 +188,9 @@ _placeholderTextKitComponents.textView.opaque = opaque; } -- (void)setLayerBacked:(BOOL)layerBacked { - ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – all instances are view-backed."); +- (void)setLayerBacked:(BOOL)layerBacked +{ + ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – instances must be view-backed in order to ensure touch events can be passed to the internal UITextView during editing."); [super setLayerBacked:layerBacked]; } From 5ecc3798ad1ec73750a434405604667bfc32e9ac Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 22 Sep 2015 16:34:40 -0700 Subject: [PATCH 08/39] Revert "Update Travis' Cocoapods version to 0.38.2" This reverts commit a9539fea1aac22f0c4922b34cbdf752f2a2061af. We can't get stuff done at Pinterest with the new version - need a day or two to get other stuff compliant. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e4c1005c7..8899c440c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: objective-c before_install: - brew update - brew reinstall xctool - - gem install cocoapods -v 0.38.2 + - gem install cocoapods -v 0.37.2 - gem install slather - xcrun simctl list install: echo "<3" From ca51a466277657eacf390079042f3a6aef229ed2 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 23 Sep 2015 10:19:47 -0700 Subject: [PATCH 09/39] Restoring 0.38.2 - Revert "Revert "Update Travis' Cocoapods version to 0.38.2"" This reverts commit 5ecc3798ad1ec73750a434405604667bfc32e9ac. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8899c440c9..9e4c1005c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: objective-c before_install: - brew update - brew reinstall xctool - - gem install cocoapods -v 0.37.2 + - gem install cocoapods -v 0.38.2 - gem install slather - xcrun simctl list install: echo "<3" From 52091eb5897bf4f8c98962342cdc52765b7b452c Mon Sep 17 00:00:00 2001 From: rcancro <@pinterest.com> Date: Wed, 23 Sep 2015 16:11:46 -0700 Subject: [PATCH 10/39] Placeholder bug in ASEditableTextNode When setting a default placeholder and an attributedString before `_textKitComponents.textView` was created, the placeholder and the string were both appearing in the text node. `_updateDisplayingPlaceholder` is called in `setAttributedString`, but since `_textKitComponents.textView` is nil the placeholder was not hidden. Calling `_updateDisplayingPlaceholder` after `_textKitComponents.textView` loads fixes this. --- AsyncDisplayKit/ASEditableTextNode.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 5e8156a86a..e6b5399edf 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -143,6 +143,7 @@ _textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string; configureTextView(_textKitComponents.textView); [self.view addSubview:_textKitComponents.textView]; + [self _updateDisplayingPlaceholder]; } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize From 47be24037dd8dda9ccc1b7a27b16fc3a32dc1fc6 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Sep 2015 19:26:00 -0700 Subject: [PATCH 11/39] Reduce _ASAsyncTransaction allocations by ensuring nodes without display methods don't create them. --- .../Private/ASDisplayNode+AsyncDisplay.mm | 37 ++++++++++++------- Podfile.lock | 2 +- examples/Kittens/Sample/KittenNode.mm | 4 +- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index e6551d7a26..ba247db343 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -308,16 +308,14 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, return BOOL(displaySentinelValue != displaySentinel.value); }; - // If we're participating in an ancestor's asyncTransaction, find it here - ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil"); - CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer; - _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; - // Set up displayBlock to call either display or draw on the delegate and return a UIImage contents asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO]; + if (!displayBlock) { return; } + + ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil"); // This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id value, BOOL canceled){ @@ -335,16 +333,27 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, } }; - if (displayBlock != NULL) { - // Call willDisplay immediately in either case - [self willDisplayAsyncLayer:self.asyncLayer]; + // Call willDisplay immediately in either case + [self willDisplayAsyncLayer:self.asyncLayer]; - if (asynchronously) { - [transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; - } else { - UIImage *contents = (UIImage *)displayBlock(); - completionBlock(contents, NO); - } + if (asynchronously) { + // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently + // while synchronizing the final application of the results to the layer's contents property (completionBlock). + + // First, look to see if we are expected to join a parent's transaction container. + CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer; + + // In the case that a transaction does not yet exist (such as for an individual node outside of a container), + // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. + // It will automatically commit the transaction at the end of the runloop. + _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; + + // Adding this displayBlock operation to the transaction will start it IMMEDIATELY. + // The only function of the transaction commit is to gate the calling of the completionBlock. + [transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; + } else { + UIImage *contents = (UIImage *)displayBlock(); + completionBlock(contents, NO); } } diff --git a/Podfile.lock b/Podfile.lock index 423c2d2851..cf8e89d790 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,4 +10,4 @@ SPEC CHECKSUMS: FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 -COCOAPODS: 0.37.2 +COCOAPODS: 0.38.2 diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index d1f49351a2..847a2629c7 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -38,7 +38,7 @@ static const CGFloat kInnerPadding = 10.0f; @implementation KittenNode -// lorem ipsum text courtesy http://kittyipsum.com/ <3 +// lorem ipsum text courtesy https://kittyipsum.com/ <3 + (NSArray *)placeholders { static NSArray *placeholders = nil; @@ -82,7 +82,7 @@ static const CGFloat kInnerPadding = 10.0f; // kitten image, with a solid background colour serving as placeholder _imageNode = [[ASNetworkImageNode alloc] init]; _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - _imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"http://placekitten.com/%zd/%zd", + _imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"https://placekitten.com/%zd/%zd", (NSInteger)roundl(_kittenSize.width), (NSInteger)roundl(_kittenSize.height)]]; // _imageNode.contentMode = UIViewContentModeCenter; From ee4b771d5de7992ea5d62e3d3b96455d1ad8c50d Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Sep 2015 19:59:12 -0700 Subject: [PATCH 12/39] Fix simple issue with ASTableViewStressTest, make Table / Collection handle changes to dataSource / delegate. --- AsyncDisplayKit/ASCollectionView.mm | 3 ++- AsyncDisplayKit/ASTableView.mm | 3 ++- examples/ASTableViewStressTest/Sample/ViewController.m | 3 --- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 946bf1cd8e..79ebd5eaa0 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -234,7 +234,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)setDataSource:(id)dataSource { - ASDisplayNodeAssert(NO, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); + // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. + ASDisplayNodeAssert(dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); } - (void)setDelegate:(id)delegate diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 47ed7778ac..c1a922d571 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -259,7 +259,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)setDataSource:(id)dataSource { - ASDisplayNodeAssert(NO, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); + // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. + ASDisplayNodeAssert(dataSource == nil, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); } - (void)setDelegate:(id)delegate diff --git a/examples/ASTableViewStressTest/Sample/ViewController.m b/examples/ASTableViewStressTest/Sample/ViewController.m index a297d37c42..f082e565f0 100644 --- a/examples/ASTableViewStressTest/Sample/ViewController.m +++ b/examples/ASTableViewStressTest/Sample/ViewController.m @@ -100,9 +100,6 @@ - (void)thrashTableView { - _tableView.asyncDelegate = self; - _tableView.asyncDataSource = self; - [_tableView reloadData]; NSArray *indexPathsAddedAndRemoved = nil; From ee0c027ba68e0299c2ccb803f84de2ed5bef0d9c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 25 Sep 2015 15:25:37 -0700 Subject: [PATCH 13/39] Build photos image request class and tests --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++ .../Details/ASPhotosImageRequest.h | 74 ++++++++ .../Details/ASPhotosImageRequest.m | 162 ++++++++++++++++++ .../ASPhotosImageRequestTests.m | 59 +++++++ 4 files changed, 307 insertions(+) create mode 100644 AsyncDisplayKit/Details/ASPhotosImageRequest.h create mode 100644 AsyncDisplayKit/Details/ASPhotosImageRequest.m create mode 100644 AsyncDisplayKitTests/ASPhotosImageRequestTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c2fe465643..09d00b2816 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -374,6 +374,9 @@ B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; + CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ASSET_TAGS = (); }; }; + CC7FD9DF1BB5E962005CCB2B /* ASPhotosImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E11BB5F750005CCB2B /* ASPhotosImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; 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 */; }; @@ -619,6 +622,9 @@ ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; + CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosImageRequest.h; sourceTree = ""; }; + CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosImageRequest.m; sourceTree = ""; }; + CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosImageRequestTests.m; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; @@ -801,6 +807,7 @@ ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, + CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, @@ -836,6 +843,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */, + CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1106,6 +1115,7 @@ 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, + CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */, ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, @@ -1493,6 +1503,7 @@ 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, 058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */, + CC7FD9DF1BB5E962005CCB2B /* ASPhotosImageRequest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1514,6 +1525,7 @@ 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */, ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */, + CC7FD9E11BB5F750005CCB2B /* ASPhotosImageRequestTests.m in Sources */, 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.h b/AsyncDisplayKit/Details/ASPhotosImageRequest.h new file mode 100644 index 0000000000..92d9910924 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPhotosImageRequest.h @@ -0,0 +1,74 @@ +// +// ASPhotosImageRequest.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import +#import + +// NS_ASSUME_NONNULL_BEGIN + +extern NSString *const ASPhotosURLScheme; + +/** + @abstract Use ASPhotosImageRequest to encapsulate all the information needed to request an image from + the Photos framework and store it in a URL. + */ +@interface ASPhotosImageRequest : NSObject + +- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; + +/** + @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. + */ ++ (/*nullable*/ ASPhotosImageRequest *)requestWithURL:(NSURL *)url; + +/** + @abstract The asset identifier for this image request provided during initialization. + */ +@property (nonatomic, readonly) NSString *assetIdentifier; + +/** + @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. + */ +@property (nonatomic) CGSize targetSize; + +/** + @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. + + @see `PHImageManager` + */ +@property (nonatomic) PHImageContentMode contentMode; + +/** + @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. + + @discussion Some properties of this object are ignored when converting this request into a URL. + As of iOS SDK 9.0, these properties are `progressHandler`, `synchronous`, and `deliveryMode`. + */ +@property (nonatomic, strong) PHImageRequestOptions *options; + +/** + @return A new URL converted from this request. + */ +@property (nonatomic, readonly) NSURL *url; + +/** + @return `YES` if `object` is an equivalent image request, `NO` otherwise. + */ +- (BOOL)isEqual:(id)object; +@end + +@interface NSURL (ASPhotosRequestConverting) + +/** + @abstract A convenience function that calls `[ASPhotosImageRequest requestWithURL:self]`. + */ +- (/*nullable*/ ASPhotosImageRequest *)asyncdisplaykit_photosRequest; + +@end + +// NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.m b/AsyncDisplayKit/Details/ASPhotosImageRequest.m new file mode 100644 index 0000000000..bc506ec916 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPhotosImageRequest.m @@ -0,0 +1,162 @@ +// +// ASPhotosImageRequest.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASPhotosImageRequest.h" +#import "ASBaseDefines.h" + +NSString *const ASPhotosURLScheme = @"ph"; + +static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; +static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; + +// value is PHImageContentMode value +static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; + +// value is PHImageRequestOptionsResizeMode value +static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; + +// value is PHImageRequestOptionsVersion value +static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; + +// value is 0 or 1 +static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; + +static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; +static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; +static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; +static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; + +@implementation ASPhotosImageRequest + +- (instancetype)init +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); + self = [self initWithAssetIdentifier:@""]; + return nil; +} + +- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier +{ + self = [super init]; + if (self) { + _assetIdentifier = assetIdentifier; + _options = [PHImageRequestOptions new]; + _contentMode = PHImageContentModeDefault; + _targetSize = PHImageManagerMaximumSize; + } + return self; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + ASPhotosImageRequest *copy = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; + copy.options = [self.options copy]; + copy.targetSize = self.targetSize; + copy.contentMode = self.contentMode; + return copy; +} + +#pragma mark Converting to URL + +- (NSURL *)url +{ + NSURLComponents *comp = [NSURLComponents new]; + comp.scheme = ASPhotosURLScheme; + comp.host = _assetIdentifier; + NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue] + , nil]; + + CGRect cropRect = _options.normalizedCropRect; + if (!CGRectIsEmpty(cropRect)) { + [queryItems addObjectsFromArray:@[ + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] + ]]; + } + comp.queryItems = queryItems; + return comp.URL; +} + +#pragma mark Converting from URL + ++ (ASPhotosImageRequest *)requestWithURL:(NSURL *)url +{ + NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + // not a photos URL + if (![comp.scheme isEqualToString:ASPhotosURLScheme]) { + return nil; + } + + ASPhotosImageRequest *request = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:url.host]; + + CGRect cropRect = CGRectZero; + CGSize targetSize = PHImageManagerMaximumSize; + for (NSURLQueryItem *item in comp.queryItems) { + if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { + request.options.networkAccessAllowed = item.value.boolValue; + } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { + targetSize.width = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { + targetSize.height = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { + request.contentMode = (PHImageContentMode)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { + request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { + cropRect.origin.x = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { + cropRect.origin.y = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { + cropRect.size.width = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { + cropRect.size.height = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { + request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; + } + } + request.targetSize = targetSize; + request.options.normalizedCropRect = cropRect; + return request; +} + +#pragma mark NSObject + +- (BOOL)isEqual:(id)object +{ + if (![object isKindOfClass:ASPhotosImageRequest.class]) { + return NO; + } + ASPhotosImageRequest *other = object; + return [other.assetIdentifier isEqualToString:self.assetIdentifier] && + other.contentMode == self.contentMode && + CGSizeEqualToSize(other.targetSize, self.targetSize) && + CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && + other.options.resizeMode == self.options.resizeMode && + other.options.version == self.options.version; +} + +@end + +@implementation NSURL (ASPhotosRequestConverting) + +- (ASPhotosImageRequest *)asyncdisplaykit_photosRequest +{ + return [ASPhotosImageRequest requestWithURL:self]; +} + +@end diff --git a/AsyncDisplayKitTests/ASPhotosImageRequestTests.m b/AsyncDisplayKitTests/ASPhotosImageRequestTests.m new file mode 100644 index 0000000000..38b8cc7170 --- /dev/null +++ b/AsyncDisplayKitTests/ASPhotosImageRequestTests.m @@ -0,0 +1,59 @@ +// +// ASPhotosImageRequestTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import +#import "ASPhotosImageRequest.h" + +static NSString *const kTestAssetID = @"testAssetID"; + +@interface ASPhotosImageRequestTests : XCTestCase + +@end + +@implementation ASPhotosImageRequestTests + +#pragma mark Example Data + ++ (ASPhotosImageRequest *)exampleImageRequest +{ + ASPhotosImageRequest *req = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:kTestAssetID]; + req.options.networkAccessAllowed = YES; + req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8); + req.targetSize = CGSizeMake(1024, 1536); + req.contentMode = PHImageContentModeAspectFill; + req.options.version = PHImageRequestOptionsVersionOriginal; + req.options.resizeMode = PHImageRequestOptionsResizeModeFast; + return req; +} + ++ (NSURL *)urlForExampleImageRequest +{ + NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID]; + return [NSURL URLWithString:str]; +} + +#pragma mark Test cases + +- (void)testThatConvertingToURLWorks +{ + XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]); +} + +- (void)testThatParsingFromURLWorks +{ + XCTAssertEqualObjects([self.class urlForExampleImageRequest].asyncdisplaykit_photosRequest, [self.class exampleImageRequest]); +} + +- (void)testThatCopyingWorks +{ + ASPhotosImageRequest *example = [self.class exampleImageRequest]; + ASPhotosImageRequest *copy = [[self.class exampleImageRequest] copy]; + XCTAssertEqualObjects(example, copy); +} + +@end From bbf9550e081bf3c4863e0de03622ce7de541284b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 25 Sep 2015 15:41:35 -0700 Subject: [PATCH 14/39] Use ASPhotosImageRequest in ASMultiplexImageNode, in a backwards-compatible way --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++- AsyncDisplayKit/ASMultiplexImageNode.h | 2 +- AsyncDisplayKit/ASMultiplexImageNode.mm | 32 +++++++------------ AsyncDisplayKit/AsyncDisplayKit.h | 1 + .../Details/ASPhotosImageRequest.h | 1 + 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 09d00b2816..92d75212a8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -374,9 +374,10 @@ B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; - CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ASSET_TAGS = (); }; }; + CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC7FD9DF1BB5E962005CCB2B /* ASPhotosImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */; settings = {ASSET_TAGS = (); }; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E21BB603FF005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -1218,6 +1219,7 @@ 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, + CC7FD9E21BB603FF005CCB2B /* ASPhotosImageRequest.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index db10daa324..ce9f22f16e 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -195,7 +195,7 @@ didFinishDownloadingImageWithIdentifier:(id)imageIdentifier * @abstract An image URL for the specified identifier. * @param imageNode The sender. * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include assets-library, Photo framework URLs (ph://), HTTP, HTTPS, and FTP URLs. If the + * @discussion Supported URLs include assets-library, URLs converted from ASPhotosImageRequest, HTTP, HTTPS, and FTP URLs. If the * image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource * multiplexImageNode:imageForImageIdentifier:]> instead. * @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available. diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index d32d3be061..987575929f 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -18,6 +18,7 @@ #import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" #import "ASLog.h" +#import "ASPhotosImageRequest.h" #if !AS_IOS8_SDK_OR_LATER #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. @@ -26,8 +27,6 @@ NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; static NSString *const kAssetsLibraryURLScheme = @"assets-library"; -static NSString *const kPHAssetURLScheme = @"ph"; -static NSString *const kPHAssetURLPrefix = @"ph://"; /** @abstract Signature for the block to be performed after an image has loaded. @@ -120,14 +119,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; /** - @abstract Loads the image corresponding to the given assetURL from the Photos framework. + @abstract Loads the image corresponding to the given image request from the Photos framework. @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param assetURL The photos framework URL (e.g., "ph://identifier") of the image to load, from PHAsset. May not be nil. + @param request The photos image request to load. May not be nil. @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. @param image The image that was loaded. May be nil if no image could be downloaded. @param error An error describing why the load failed, if it failed; nil otherwise. */ -- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; +- (void)_loadPHAssetWithRequest:(ASPhotosImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; /** @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. @@ -456,8 +455,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. - else if (AS_AT_LEAST_IOS8 && [[nextImageURL scheme] isEqualToString:kPHAssetURLScheme]) { - [self _loadPHAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *image, NSError *error) { + else if (ASPhotosImageRequest *request = nextImageURL.asyncdisplaykit_photosRequest) { + [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier); finishedLoadingBlock(image, nextImageIdentifier, error); }]; @@ -511,17 +510,15 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } -- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock +- (void)_loadPHAssetWithRequest:(ASPhotosImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock { ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7."); ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); + ASDisplayNodeAssertNotNil(request, @"request is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); // Get the PHAsset itself. - ASDisplayNodeAssertTrue([[assetURL absoluteString] hasPrefix:kPHAssetURLPrefix]); - NSString *assetIdentifier = [[assetURL absoluteString] substringFromIndex:[kPHAssetURLPrefix length]]; - PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil]; + PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil]; if ([assetFetchResult count] == 0) { // Error. completionBlock(nil, nil); @@ -531,15 +528,10 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Get the best image we can. PHAsset *imageAsset = [assetFetchResult firstObject]; - PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init]; - requestOptions.version = PHImageRequestOptionsVersionCurrent; - requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - requestOptions.resizeMode = PHImageRequestOptionsResizeModeNone; - [[PHImageManager defaultManager] requestImageForAsset:imageAsset - targetSize:CGSizeMake(2048.0, 2048.0) // Ideally we would use PHImageManagerMaximumSize and kill the options, but we get back nil when requesting images of video assets. rdar://18447788 - contentMode:PHImageContentModeDefault - options:requestOptions + targetSize:request.targetSize + contentMode:request.contentMode + options:request.options resultHandler:^(UIImage *image, NSDictionary *info) { completionBlock(image, info[PHImageErrorKey]); }]; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 6b2aa9ea9d..57fe043902 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -18,6 +18,7 @@ #import #import #import +#import #import #import diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.h b/AsyncDisplayKit/Details/ASPhotosImageRequest.h index 92d9910924..45e34c9042 100644 --- a/AsyncDisplayKit/Details/ASPhotosImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosImageRequest.h @@ -60,6 +60,7 @@ extern NSString *const ASPhotosURLScheme; @return `YES` if `object` is an equivalent image request, `NO` otherwise. */ - (BOOL)isEqual:(id)object; + @end @interface NSURL (ASPhotosRequestConverting) From e787c4041116594314b5d16405043286ece0bf29 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 25 Sep 2015 15:45:14 -0700 Subject: [PATCH 15/39] Only parse photos URL after we check the scheme --- AsyncDisplayKit/Details/ASPhotosImageRequest.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.m b/AsyncDisplayKit/Details/ASPhotosImageRequest.m index bc506ec916..4653f8a049 100644 --- a/AsyncDisplayKit/Details/ASPhotosImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosImageRequest.m @@ -96,12 +96,13 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; + (ASPhotosImageRequest *)requestWithURL:(NSURL *)url { - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; // not a photos URL - if (![comp.scheme isEqualToString:ASPhotosURLScheme]) { + if (![url.scheme isEqualToString:ASPhotosURLScheme]) { return nil; } + NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + ASPhotosImageRequest *request = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:url.host]; CGRect cropRect = CGRectZero; From 06556d04a3d89398a48625fbbee6c6f7a9828b46 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 25 Sep 2015 15:54:17 -0700 Subject: [PATCH 16/39] ASMultiplexImageNode uses high quality format when requesting images --- AsyncDisplayKit/ASMultiplexImageNode.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 987575929f..841e2b64e9 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -527,11 +527,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Get the best image we can. PHAsset *imageAsset = [assetFetchResult firstObject]; + PHImageRequestOptions *options = [request.options copy]; + options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; [[PHImageManager defaultManager] requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode - options:request.options + options:options resultHandler:^(UIImage *image, NSDictionary *info) { completionBlock(image, info[PHImageErrorKey]); }]; From bf88bd8e197f9495f2a10a6815700fdc947c7d3d Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 26 Sep 2015 13:40:00 -0700 Subject: [PATCH 17/39] Rename image request class, add deliveryMode into the request params --- AsyncDisplayKit.xcodeproj/project.pbxproj | 28 +++++++++---------- AsyncDisplayKit/ASMultiplexImageNode.h | 2 +- AsyncDisplayKit/ASMultiplexImageNode.mm | 8 +++--- AsyncDisplayKit/AsyncDisplayKit.h | 2 +- ...uest.h => ASPhotosFrameworkImageRequest.h} | 16 ++++++----- ...uest.m => ASPhotosFrameworkImageRequest.m} | 28 +++++++++++-------- ...m => ASPhotosFrameworkImageRequestTests.m} | 16 +++++------ 7 files changed, 54 insertions(+), 46 deletions(-) rename AsyncDisplayKit/Details/{ASPhotosImageRequest.h => ASPhotosFrameworkImageRequest.h} (68%) rename AsyncDisplayKit/Details/{ASPhotosImageRequest.m => ASPhotosFrameworkImageRequest.m} (82%) rename AsyncDisplayKitTests/{ASPhotosImageRequestTests.m => ASPhotosFrameworkImageRequestTests.m} (70%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 92d75212a8..3cf1189c0b 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -374,10 +374,10 @@ B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; - CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC7FD9DF1BB5E962005CCB2B /* ASPhotosImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */; settings = {ASSET_TAGS = (); }; }; - CC7FD9E11BB5F750005CCB2B /* ASPhotosImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; - CC7FD9E21BB603FF005CCB2B /* ASPhotosImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -623,9 +623,9 @@ ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; - CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosImageRequest.h; sourceTree = ""; }; - CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosImageRequest.m; sourceTree = ""; }; - CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosImageRequestTests.m; sourceTree = ""; }; + CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; + CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; + CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; @@ -808,7 +808,7 @@ ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, - CC7FD9E01BB5F750005CCB2B /* ASPhotosImageRequestTests.m */, + CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, @@ -844,8 +844,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( - CC7FD9DC1BB5E962005CCB2B /* ASPhotosImageRequest.h */, - CC7FD9DD1BB5E962005CCB2B /* ASPhotosImageRequest.m */, + CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, + CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1116,7 +1116,7 @@ 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, - CC7FD9DE1BB5E962005CCB2B /* ASPhotosImageRequest.h in Headers */, + CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */, ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, @@ -1219,7 +1219,7 @@ 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, - CC7FD9E21BB603FF005CCB2B /* ASPhotosImageRequest.h in Headers */, + CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, @@ -1505,7 +1505,7 @@ 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, 058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */, - CC7FD9DF1BB5E962005CCB2B /* ASPhotosImageRequest.m in Sources */, + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1527,7 +1527,7 @@ 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */, ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */, - CC7FD9E11BB5F750005CCB2B /* ASPhotosImageRequestTests.m in Sources */, + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */, 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index ce9f22f16e..5b811f6654 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -195,7 +195,7 @@ didFinishDownloadingImageWithIdentifier:(id)imageIdentifier * @abstract An image URL for the specified identifier. * @param imageNode The sender. * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include assets-library, URLs converted from ASPhotosImageRequest, HTTP, HTTPS, and FTP URLs. If the + * @discussion Supported URLs include assets-library, URLs converted from ASPhotosFrameworkImageRequest, HTTP, HTTPS, and FTP URLs. If the * image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource * multiplexImageNode:imageForImageIdentifier:]> instead. * @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available. diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 841e2b64e9..c3028b0123 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -18,7 +18,7 @@ #import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" #import "ASLog.h" -#import "ASPhotosImageRequest.h" +#import "ASPhotosFrameworkImageRequest.h" #if !AS_IOS8_SDK_OR_LATER #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. @@ -126,7 +126,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @param image The image that was loaded. May be nil if no image could be downloaded. @param error An error describing why the load failed, if it failed; nil otherwise. */ -- (void)_loadPHAssetWithRequest:(ASPhotosImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; +- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; /** @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. @@ -455,7 +455,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. - else if (ASPhotosImageRequest *request = nextImageURL.asyncdisplaykit_photosRequest) { + else if (ASPhotosFrameworkImageRequest *request = nextImageURL.asyncdisplaykit_photosRequest) { [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier); finishedLoadingBlock(image, nextImageIdentifier, error); @@ -510,7 +510,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } -- (void)_loadPHAssetWithRequest:(ASPhotosImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock +- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock { ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7."); ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 57fe043902..2f4d77d15b 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -18,7 +18,7 @@ #import #import #import -#import +#import #import #import diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h similarity index 68% rename from AsyncDisplayKit/Details/ASPhotosImageRequest.h rename to AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h index 45e34c9042..ebf04fdfbc 100644 --- a/AsyncDisplayKit/Details/ASPhotosImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -1,5 +1,5 @@ // -// ASPhotosImageRequest.h +// ASPhotosFrameworkImageRequest.h // AsyncDisplayKit // // Created by Adlai Holler on 9/25/15. @@ -14,17 +14,17 @@ extern NSString *const ASPhotosURLScheme; /** - @abstract Use ASPhotosImageRequest to encapsulate all the information needed to request an image from + @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from the Photos framework and store it in a URL. */ -@interface ASPhotosImageRequest : NSObject +@interface ASPhotosFrameworkImageRequest : NSObject - (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; /** @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. */ -+ (/*nullable*/ ASPhotosImageRequest *)requestWithURL:(NSURL *)url; ++ (/*nullable*/ ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; /** @abstract The asset identifier for this image request provided during initialization. @@ -47,7 +47,9 @@ extern NSString *const ASPhotosURLScheme; @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler`, `synchronous`, and `deliveryMode`. + As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. + Note that PHImageRequestOptionsDeliveryModeOpportunistic is not recommended when using ASMultiplexImageNode, + because it sends multiple images and only the first will be accepted. */ @property (nonatomic, strong) PHImageRequestOptions *options; @@ -66,9 +68,9 @@ extern NSString *const ASPhotosURLScheme; @interface NSURL (ASPhotosRequestConverting) /** - @abstract A convenience function that calls `[ASPhotosImageRequest requestWithURL:self]`. + @abstract A convenience function that calls `[ASPhotosFrameworkImageRequest requestWithURL:self]`. */ -- (/*nullable*/ ASPhotosImageRequest *)asyncdisplaykit_photosRequest; +- (/*nullable*/ ASPhotosFrameworkImageRequest *)asyncdisplaykit_photosRequest; @end diff --git a/AsyncDisplayKit/Details/ASPhotosImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m similarity index 82% rename from AsyncDisplayKit/Details/ASPhotosImageRequest.m rename to AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m index 4653f8a049..dcf47957c6 100644 --- a/AsyncDisplayKit/Details/ASPhotosImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -1,12 +1,12 @@ // -// ASPhotosImageRequest.m +// ASPhotosFrameworkImageRequest.m // AsyncDisplayKit // // Created by Adlai Holler on 9/25/15. // Copyright © 2015 Facebook. All rights reserved. // -#import "ASPhotosImageRequest.h" +#import "ASPhotosFrameworkImageRequest.h" #import "ASBaseDefines.h" NSString *const ASPhotosURLScheme = @"ph"; @@ -20,6 +20,9 @@ static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; // value is PHImageRequestOptionsResizeMode value static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; +// value is PHImageRequestOptionsDeliveryMode value +static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; + // value is PHImageRequestOptionsVersion value static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; @@ -31,7 +34,7 @@ static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; -@implementation ASPhotosImageRequest +@implementation ASPhotosFrameworkImageRequest - (instancetype)init { @@ -56,7 +59,7 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - (id)copyWithZone:(NSZone *)zone { - ASPhotosImageRequest *copy = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; + ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; copy.options = [self.options copy]; copy.targetSize = self.targetSize; copy.contentMode = self.contentMode; @@ -76,7 +79,8 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue] + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] , nil]; CGRect cropRect = _options.normalizedCropRect; @@ -94,7 +98,7 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; #pragma mark Converting from URL -+ (ASPhotosImageRequest *)requestWithURL:(NSURL *)url ++ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url { // not a photos URL if (![url.scheme isEqualToString:ASPhotosURLScheme]) { @@ -103,7 +107,7 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - ASPhotosImageRequest *request = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:url.host]; + ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; CGRect cropRect = CGRectZero; CGSize targetSize = PHImageManagerMaximumSize; @@ -128,6 +132,8 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; cropRect.size.height = item.value.doubleValue; } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { + request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; } } request.targetSize = targetSize; @@ -139,10 +145,10 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:ASPhotosImageRequest.class]) { + if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { return NO; } - ASPhotosImageRequest *other = object; + ASPhotosFrameworkImageRequest *other = object; return [other.assetIdentifier isEqualToString:self.assetIdentifier] && other.contentMode == self.contentMode && CGSizeEqualToSize(other.targetSize, self.targetSize) && @@ -155,9 +161,9 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; @implementation NSURL (ASPhotosRequestConverting) -- (ASPhotosImageRequest *)asyncdisplaykit_photosRequest +- (ASPhotosFrameworkImageRequest *)asyncdisplaykit_photosRequest { - return [ASPhotosImageRequest requestWithURL:self]; + return [ASPhotosFrameworkImageRequest requestWithURL:self]; } @end diff --git a/AsyncDisplayKitTests/ASPhotosImageRequestTests.m b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m similarity index 70% rename from AsyncDisplayKitTests/ASPhotosImageRequestTests.m rename to AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m index 38b8cc7170..5c276706b4 100644 --- a/AsyncDisplayKitTests/ASPhotosImageRequestTests.m +++ b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m @@ -1,5 +1,5 @@ // -// ASPhotosImageRequestTests.m +// ASPhotosFrameworkImageRequestTests.m // AsyncDisplayKit // // Created by Adlai Holler on 9/25/15. @@ -7,21 +7,21 @@ // #import -#import "ASPhotosImageRequest.h" +#import "ASPhotosFrameworkImageRequest.h" static NSString *const kTestAssetID = @"testAssetID"; -@interface ASPhotosImageRequestTests : XCTestCase +@interface ASPhotosFrameworkImageRequestTests : XCTestCase @end -@implementation ASPhotosImageRequestTests +@implementation ASPhotosFrameworkImageRequestTests #pragma mark Example Data -+ (ASPhotosImageRequest *)exampleImageRequest ++ (ASPhotosFrameworkImageRequest *)exampleImageRequest { - ASPhotosImageRequest *req = [[ASPhotosImageRequest alloc] initWithAssetIdentifier:kTestAssetID]; + ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID]; req.options.networkAccessAllowed = YES; req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8); req.targetSize = CGSizeMake(1024, 1536); @@ -51,8 +51,8 @@ static NSString *const kTestAssetID = @"testAssetID"; - (void)testThatCopyingWorks { - ASPhotosImageRequest *example = [self.class exampleImageRequest]; - ASPhotosImageRequest *copy = [[self.class exampleImageRequest] copy]; + ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest]; + ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy]; XCTAssertEqualObjects(example, copy); } From ffa0829fc932b98f4f4e3559c405d78cd9851595 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 26 Sep 2015 18:41:38 -0700 Subject: [PATCH 18/39] Update & document changes to ASPhotosFrameworkImageRequest handling of PHImageRequestOptionsDeliveryMode --- AsyncDisplayKit/ASMultiplexImageNode.mm | 4 +++- AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index c3028b0123..a3cd99558d 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -528,7 +528,9 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // Get the best image we can. PHAsset *imageAsset = [assetFetchResult firstObject]; PHImageRequestOptions *options = [request.options copy]; - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { + options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; + } [[PHImageManager defaultManager] requestImageForAsset:imageAsset targetSize:request.targetSize diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h index ebf04fdfbc..4f55b0c401 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -48,8 +48,8 @@ extern NSString *const ASPhotosURLScheme; @discussion Some properties of this object are ignored when converting this request into a URL. As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - Note that PHImageRequestOptionsDeliveryModeOpportunistic is not recommended when using ASMultiplexImageNode, - because it sends multiple images and only the first will be accepted. + Note that ASMultiplexImageNode does not support PHImageRequestOptionsDeliveryModeOpportunistic + because it sends multiple images, and that mode will be replaced by PHImageRequestOptionsDeliveryModeHighQualityFormat */ @property (nonatomic, strong) PHImageRequestOptions *options; From f6fe3b07d831ab8681f98ae29f2e44f83151199a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 26 Sep 2015 19:17:43 -0700 Subject: [PATCH 19/39] Request and receive images in background queue --- AsyncDisplayKit/ASMultiplexImageNode.mm | 50 +++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index a3cd99558d..beb21e3a65 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -516,29 +516,33 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); ASDisplayNodeAssertNotNil(request, @"request is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // Get the PHAsset itself. - PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil]; - if ([assetFetchResult count] == 0) { - // Error. - completionBlock(nil, nil); - return; - } - - // Get the best image we can. - PHAsset *imageAsset = [assetFetchResult firstObject]; - PHImageRequestOptions *options = [request.options copy]; - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - } - - [[PHImageManager defaultManager] requestImageForAsset:imageAsset - targetSize:request.targetSize - contentMode:request.contentMode - options:options - resultHandler:^(UIImage *image, NSDictionary *info) { - completionBlock(image, info[PHImageErrorKey]); - }]; + + // This is sometimes called on main but there's no reason to stay there + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + // Get the PHAsset itself. + PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil]; + if ([assetFetchResult count] == 0) { + // Error. + completionBlock(nil, nil); + return; + } + + PHAsset *imageAsset = [assetFetchResult firstObject]; + PHImageRequestOptions *options = [request.options copy]; + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { + // Without this flag the result will be delivered on the main queue, which is pointless + // But synchronous -> HighQualityFormat so we only use it if high quality format is specified + options.synchronous = YES; + } + + [[PHImageManager defaultManager] requestImageForAsset:imageAsset + targetSize:request.targetSize + contentMode:request.contentMode + options:options + resultHandler:^(UIImage *image, NSDictionary *info) { + completionBlock(image, info[PHImageErrorKey]); + }]; + }); } - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock From 5f49e0f67d4960d50b651bb4e6b01640ecbab277 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 26 Sep 2015 19:50:27 -0700 Subject: [PATCH 20/39] If Photos framework image is delivered on main queue, dispatch async to background queue --- AsyncDisplayKit/ASMultiplexImageNode.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index beb21e3a65..026c1680a6 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -540,7 +540,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { - completionBlock(image, info[PHImageErrorKey]); + if (NSThread.isMainThread) { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + completionBlock(image, info[PHImageErrorKey]); + }); + } else { + completionBlock(image, info[PHImageErrorKey]); + } }]; }); } From e619edcd86f37de4ae657c22466033b5e653698a Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 27 Sep 2015 22:59:36 +0300 Subject: [PATCH 21/39] Ensure data consistency between ASDataController and its delegate while executing update transactions. --- AsyncDisplayKit/Details/ASDataController.mm | 35 ++++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 3b80baecd0..7a6036869e 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -24,7 +24,8 @@ const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this. + NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. + NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. @@ -39,6 +40,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @property (atomic, assign) NSUInteger batchUpdateCounter; +/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. +- (NSMutableArray *)externalCompletedNodes; + @end @implementation ASDataController @@ -347,6 +351,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ ASDisplayNodePerformBlockOnMainThread(^{ + // Deep copy _completedNodes to _externalCompletedNodes. + // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. + _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes); + LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; }); @@ -363,6 +371,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ ASDisplayNodePerformBlockOnMainThread(^{ + // Now that the transaction is done, _completedNodes can be accessed externally again. + _externalCompletedNodes = nil; + LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }); @@ -617,28 +628,31 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSUInteger)numberOfSections { ASDisplayNodeAssertMainThread(); - return [_completedNodes count]; + return [[self externalCompletedNodes] count]; } - (NSUInteger)numberOfRowsInSection:(NSUInteger)section { ASDisplayNodeAssertMainThread(); - return [_completedNodes[section] count]; + return [[self externalCompletedNodes][section] count]; } - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return _completedNodes[indexPath.section][indexPath.row]; + return [self externalCompletedNodes][indexPath.section][indexPath.row]; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; { ASDisplayNodeAssertMainThread(); + NSArray *nodes = [self externalCompletedNodes]; + NSUInteger numberOfNodes = nodes.count; + // Loop through each section to look for the cellNode - for (NSUInteger i = 0; i < [_completedNodes count]; i++) { - NSArray *sectionNodes = _completedNodes[i]; + for (NSUInteger i = 0; i < numberOfNodes; i++) { + NSArray *sectionNodes = nodes[i]; NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; if (cellIndex != NSNotFound) { return [NSIndexPath indexPathForRow:cellIndex inSection:i]; @@ -651,13 +665,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); - return ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]); + return ASFindElementsInMultidimensionalArrayAtIndexPaths([self externalCompletedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _completedNodes; + return [self externalCompletedNodes]; +} + +- (NSMutableArray *)externalCompletedNodes +{ + return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes; } #pragma mark - Dealloc From a94cd9dd990aa5bc7a2617c33aab8d8207f2e4c6 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 28 Sep 2015 00:18:49 +0300 Subject: [PATCH 22/39] Remove externalCompletedNodes getter in ASDataController. --- AsyncDisplayKit/Details/ASDataController.mm | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7a6036869e..693f1653cc 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -40,9 +40,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; @property (atomic, assign) NSUInteger batchUpdateCounter; -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. -- (NSMutableArray *)externalCompletedNodes; - @end @implementation ASDataController @@ -628,26 +625,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSUInteger)numberOfSections { ASDisplayNodeAssertMainThread(); - return [[self externalCompletedNodes] count]; + return [[self completedNodes] count]; } - (NSUInteger)numberOfRowsInSection:(NSUInteger)section { ASDisplayNodeAssertMainThread(); - return [[self externalCompletedNodes][section] count]; + return [[self completedNodes][section] count]; } - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return [self externalCompletedNodes][indexPath.section][indexPath.row]; + return [self completedNodes][indexPath.section][indexPath.row]; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; { ASDisplayNodeAssertMainThread(); - NSArray *nodes = [self externalCompletedNodes]; + NSArray *nodes = [self completedNodes]; NSUInteger numberOfNodes = nodes.count; // Loop through each section to look for the cellNode @@ -665,17 +662,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); - return ASFindElementsInMultidimensionalArrayAtIndexPaths([self externalCompletedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); + return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } +/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return [self externalCompletedNodes]; -} - -- (NSMutableArray *)externalCompletedNodes -{ return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes; } From a58844379c6b9a56e0d1c6af2ac1a6bd38535a81 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 27 Sep 2015 19:14:36 -0700 Subject: [PATCH 23/39] Implementation of Synchronous Concurrency features for AsyncDisplayKit 2.0 This provides internal features on _ASAsyncTransaction and ASDisplayNode to facilitate implementing public API that allows clients to choose if they would prefer to block on the completion of unfinished rendering, rather than allow a placeholder state to become visible. The internal features are: -[_ASAsyncTransaction waitUntilComplete] -[ASDisplayNode recursivelyEnsureDisplay] Also provided are two such implementations: -[ASCellNode setNeverShowPlaceholders:], which integrates with both Tables and Collections -[ASViewController setNeverShowPlaceholders:], which should work with Nav and Tab controllers. Lastly, on ASDisplayNode, a new property .shouldBypassEnsureDisplay allows individual node types to exempt themselves from blocking the main thread on their display. By implementing the feature at the ASCellNode level rather than ASTableView & ASCollectionView, developers can retain fine-grained control on display characteristics. For example, certain cell types may be appropriate to display to the user with placeholders, whereas others may not. Follow-up work will include unit tests, revisiting names, and the header locations of definitions. --- AsyncDisplayKit/ASCellNode.h | 23 ++ AsyncDisplayKit/ASCollectionView.h | 9 + AsyncDisplayKit/ASCollectionView.mm | 23 +- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 32 ++ AsyncDisplayKit/ASDisplayNode.mm | 64 ++++ AsyncDisplayKit/ASMultiplexImageNode.mm | 1 + AsyncDisplayKit/ASNetworkImageNode.mm | 1 + AsyncDisplayKit/ASTableView.h | 42 +- AsyncDisplayKit/ASTableView.mm | 8 + AsyncDisplayKit/ASViewController.h | 6 +- AsyncDisplayKit/ASViewController.m | 18 +- .../Transactions/_ASAsyncTransaction.h | 8 + .../Transactions/_ASAsyncTransaction.m | 69 +++- .../Transactions/_ASAsyncTransactionGroup.h | 1 + .../Transactions/_ASAsyncTransactionGroup.m | 5 + .../Private/ASDisplayNodeInternal.h | 1 + .../Default-568h@2x.png | Bin 0 -> 17520 bytes .../Default-667h@2x.png | Bin 0 -> 18314 bytes .../Default-736h@3x.png | Bin 0 -> 23380 bytes examples/SynchronousConcurrency/Podfile | 3 + .../Sample.xcodeproj/project.pbxproj | 361 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/Sample.xcscheme | 88 +++++ .../contents.xcworkspacedata | 1 + .../Sample/AppDelegate.h | 20 + .../Sample/AppDelegate.m | 33 ++ .../Sample/AsyncTableViewController.h | 16 + .../Sample/AsyncTableViewController.m | 91 +++++ .../Sample/AsyncViewController.h | 13 + .../Sample/AsyncViewController.m | 37 ++ .../SynchronousConcurrency/Sample/Info.plist | 36 ++ .../Sample/RandomCoreGraphicsNode.h | 16 + .../Sample/RandomCoreGraphicsNode.m | 93 +++++ examples/SynchronousConcurrency/Sample/main.m | 20 + 34 files changed, 1109 insertions(+), 37 deletions(-) create mode 100644 examples/SynchronousConcurrency/Default-568h@2x.png create mode 100644 examples/SynchronousConcurrency/Default-667h@2x.png create mode 100644 examples/SynchronousConcurrency/Default-736h@3x.png create mode 100644 examples/SynchronousConcurrency/Podfile create mode 100644 examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj create mode 100644 examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme create mode 100644 examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/SynchronousConcurrency/Sample/AppDelegate.h create mode 100644 examples/SynchronousConcurrency/Sample/AppDelegate.m create mode 100644 examples/SynchronousConcurrency/Sample/AsyncTableViewController.h create mode 100644 examples/SynchronousConcurrency/Sample/AsyncTableViewController.m create mode 100644 examples/SynchronousConcurrency/Sample/AsyncViewController.h create mode 100644 examples/SynchronousConcurrency/Sample/AsyncViewController.m create mode 100644 examples/SynchronousConcurrency/Sample/Info.plist create mode 100644 examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h create mode 100644 examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m create mode 100644 examples/SynchronousConcurrency/Sample/main.m diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index db859eea1d..ae061eb6ca 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -14,6 +14,29 @@ */ @interface ASCellNode : ASDisplayNode +/** + * @abstract When enabled, ensures that the cell is completely displayed before allowed onscreen. + * + * @default NO + * @discussion Normally, ASCellNodes are preloaded and have finished display before they are onscreen. + * However, if the Table or Collection's rangeTuningParameters are set to small values (or 0), + * or if the user is scrolling rapidly on a slow device, it is possible for a cell's display to + * be incomplete when it becomes visible. + * + * In this case, normally placeholder states are shown and scrolling continues uninterrupted. + * The finished, drawn content is then shown as soon as it is ready. + * + * With this property set to YES, the main thread will be blocked until display is complete for + * the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually + * indistinguishible from UIKit's, except being faster. + * + * Using this option does not eliminate all of the performance advantages of AsyncDisplayKit. + * Normally, a cell has been preloading and is almost done when it reaches the screen, so the + * blocking time is very short. If the rangeTuningParameters are set to 0, still this option + * outperforms UIKit: while the main thread is waiting, subnode display executes concurrently. + */ +@property (nonatomic, assign) BOOL neverShowPlaceholders; + /* * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. */ diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 4756835551..d8f78c6e13 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -212,6 +212,15 @@ */ - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath; +/** + * Similar to -indexPathForCell:. + * + * @param cellNode a cellNode part of the table view + * + * @returns an indexPath for this cellNode + */ +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; + /** * Similar to -visibleCells. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 79ebd5eaa0..b1b29335b3 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -17,6 +17,9 @@ #import "UICollectionViewLayout+ASConvenience.h" #import "ASInternalHelpers.h" +// FIXME: Temporary nonsense import until method names are finalized and exposed +#import "ASDisplayNode+Subclasses.h" + const static NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; @@ -315,6 +318,16 @@ static BOOL _isInterceptedSelector(SEL sel) return [[_dataController nodeAtIndexPath:indexPath] calculatedSize]; } +- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController nodeAtIndexPath:indexPath]; +} + +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode +{ + return [_dataController indexPathForNode:cellNode]; +} + - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; @@ -392,11 +405,6 @@ static BOOL _isInterceptedSelector(SEL sel) [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController nodeAtIndexPath:indexPath]; -} - #pragma mark - #pragma mark Intercepted selectors. @@ -490,6 +498,11 @@ static BOOL _isInterceptedSelector(SEL sel) if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } + + ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; + if (cellNode.neverShowPlaceholders) { + [cellNode recursivelyEnsureDisplay]; + } } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 44b81ac1b1..167c94de7d 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -429,6 +429,38 @@ // This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, // but it's considered private API for now and its use should not be encouraged. - (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass; + +// The two methods below will eventually be exposed, but their names are subject to change. +/** + * @abstract Ensure that all rendering is complete for this node and its descendents. + * + * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that + * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController + * to implement their respective ".neverShowPlaceholders" option. + * + * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. + * + * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an + * asynchronous display operation, and some already finished. + * + * In order to guarantee against deadlocks, this method should only be called on the main thread. + * It may block on the private queue, [_ASDisplayLayer displayQueue] + */ +- (void)recursivelyEnsureDisplay; + +/** + * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. + * + * @discussion Nodes that are expensive to draw and expected to have placeholder even with + * .neverShowPlaceholders enabled should set this to YES. + * + * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. + * + * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, + * and are expected to support a placeholder state given that display is often blocked on slow data fetching. + */ +@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; + @end #define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity") diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 323ade43e5..358e4c1428 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -14,6 +14,7 @@ #import #import "_ASAsyncTransaction.h" +#import "_ASAsyncTransactionContainer+Private.h" #import "_ASPendingState.h" #import "_ASDisplayView.h" #import "_ASScopeTimer.h" @@ -1415,6 +1416,68 @@ static NSInteger incrementIfFound(NSInteger i) { [_placeholderLayer removeFromSuperlayer]; } +void recursivelyEnsureDisplayForLayer(CALayer *layer) +{ + // This recursion must handle layers in various states: + // 1. Just added to hierarchy, CA hasn't yet called -display + // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) + // 3. Has no content to display at all + // Specifically for case 1), we need to explicitly trigger a -display call now. + // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit + // (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]) { + // 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]; + } + + // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. + for (CALayer *sublayer in layer.sublayers) { + recursivelyEnsureDisplayForLayer(sublayer); + } + + // As the recursion unwinds, verify each transaction is complete and block if it is not. + // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. + BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); + if (waitUntilComplete) { + for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { + // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. + // This significantly reduces time on the main thread relative to UIKit. + [transaction waitUntilComplete]; + } + } +} + +- (void)recursivelyEnsureDisplay +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay"); + ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay"); + + CALayer *layer = self.layer; + // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, + // so we should call it outside of starting the recursion below. If our own layer is not marked + // as dirty, we can assume layout has run on this subtree before. + if ([layer needsLayout]) { + [layer layoutIfNeeded]; + } + recursivelyEnsureDisplayForLayer(layer); +} + +- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay +{ + ASDN::MutexLocker l(_propertyLock); + _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; +} + +- (BOOL)shouldBypassEnsureDisplay +{ + ASDN::MutexLocker l(_propertyLock); + return _flags.shouldBypassEnsureDisplay; +} + #pragma mark - For Subclasses - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize @@ -1484,6 +1547,7 @@ static NSInteger incrementIfFound(NSInteger i) { ASDN::MutexLocker l(_propertyLock); return _preferredFrameSize; } + - (UIImage *)placeholderImage { return nil; diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index d32d3be061..b5c9911cac 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -158,6 +158,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; + self.shouldBypassEnsureDisplay = YES; return self; } diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 658176eeee..d41fd60976 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -44,6 +44,7 @@ _cache = cache; _downloader = downloader; _shouldCacheImage = YES; + self.shouldBypassEnsureDisplay = YES; return self; } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index c562f83f2f..b7588f782b 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -27,8 +27,28 @@ */ @interface ASTableView : UITableView -@property (nonatomic, weak) id asyncDataSource; @property (nonatomic, weak) id asyncDelegate; // must not be nil +@property (nonatomic, weak) id asyncDataSource; + +/** + * Initializer. + * + * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. + * The frame of the table view changes as table cells are added and deleted. + * + * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. + * + * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. + * + * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and + * `tableView: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 `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` 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 style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; /** * Tuning parameters for a range. @@ -50,26 +70,6 @@ */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - * - * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and - * `tableView: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 `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` 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 style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; - /** * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index c1a922d571..7c36c775a6 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -18,6 +18,9 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" +// FIXME: Temporary nonsense import until method names are finalized and exposed +#import "ASDisplayNode+Subclasses.h" + //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -569,6 +572,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } + + ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath]; + if (cellNode.neverShowPlaceholders) { + [cellNode recursivelyEnsureDisplay]; + } } - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index e3b18661fe..f617ed4c43 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -13,7 +13,11 @@ @property (nonatomic, strong, readonly) ASDisplayNode *node; -//TODO Use nonnull annotation late on. Travis doesn't recognize it (yet). +// 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; @end diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 2a9f555178..ef89b905a6 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -10,7 +10,13 @@ #import "ASAssert.h" #import "ASDimension.h" +// FIXME: Temporary nonsense import until method names are finalized and exposed +#import "ASDisplayNode+Subclasses.h" + @implementation ASViewController +{ + BOOL _ensureDisplayed; +} - (instancetype)initWithNode:(ASDisplayNode *)node { @@ -33,15 +39,25 @@ - (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; CGSize viewSize = self.view.bounds.size; ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize); [_node measureWithSizeRange:constrainedSize]; - [super viewWillLayoutSubviews]; +} + +- (void)viewDidLayoutSubviews +{ + if (_ensureDisplayed && self.neverShowPlaceholders) { + _ensureDisplayed = NO; + [self.node recursivelyEnsureDisplay]; + } + [super viewDidLayoutSubviews]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + _ensureDisplayed = YES; [_node recursivelyFetchData]; } diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h index 1558766c40..ca792f9fec 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h @@ -26,6 +26,7 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) { ASAsyncTransactionStateOpen = 0, ASAsyncTransactionStateCommitted, ASAsyncTransactionStateCanceled, + ASAsyncTransactionStateComplete }; /** @@ -54,6 +55,13 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) { - (id)initWithCallbackQueue:(dispatch_queue_t)callbackQueue completionBlock:(asyncdisplaykit_async_transaction_completion_block_t)completionBlock; +/** + @summary Block the main thread until the transaction is complete, including callbacks. + + @desc This must be called on the main thread. + */ +- (void)waitUntilComplete; + /** The dispatch queue that the completion blocks will be called on. */ diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m index 0457d7a9ed..37ba85dd53 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m @@ -7,7 +7,7 @@ */ #import "_ASAsyncTransaction.h" - +#import "_ASAsyncTransactionGroup.h" #import "ASAssert.h" @interface ASDisplayNodeAsyncTransactionOperation : NSObject @@ -40,6 +40,11 @@ } } +- (NSString *)description +{ + return [NSString stringWithFormat:@"", self, (unsigned long)_state, _group, _operations]; +} + @end diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h index 235e98a1cf..c7b3da3217 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h @@ -15,6 +15,7 @@ @interface _ASAsyncTransactionGroup : NSObject /// The main transaction group is scheduled to commit on every tick of the main runloop. + (instancetype)mainTransactionGroup; ++ (void)commit; /// Add a transaction container to be committed. /// @param containerLayer A layer containing a transaction to be commited. May or may not be a container layer. diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m index c78a42d047..afebd4e5cc 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m @@ -95,6 +95,11 @@ static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observ } } ++ (void)commit +{ + [[_ASAsyncTransactionGroup mainTransactionGroup] commit]; +} + @end static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 7066159c68..8283438272 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -80,6 +80,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { unsigned layerBacked:1; unsigned displaysAsynchronously:1; unsigned shouldRasterizeDescendants:1; + unsigned shouldBypassEnsureDisplay:1; unsigned displaySuspended:1; // whether custom drawing is enabled diff --git a/examples/SynchronousConcurrency/Default-568h@2x.png b/examples/SynchronousConcurrency/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee80b93937cd9dd79502629b14fe09aa03cacc2 GIT binary patch literal 17520 zcmeI3&2QsG7{;dyhuxq(aR70$vO)rco)4~Hqb-mA2=CIb8^QK*gwP8wCVy+_i!WbB=&euO z!=w19^{!?6gA#W9HYtq<0qu=Ybz>Z0`-H?on{-{TR{Zmu?}~!!)QWeFmfQ*&q~~s* zhveXV_s~8+u`5n-qh6?vEov|zF&4&yz86{JS~2yt=yB346@|1*d{QfJCIbpbtv#XP zheR++hG@%*E|`^)Vkml9c~ekjMU!MrQZ!LfExBSThA{aQ>jipL4V{j)-@H8;jz+a& zFOCCCl18IZX{43>uq!E*N=1@YNmWJKLyXS67>`9Sx|NwseVQb)LpO+B-xCsF-1diY ztyoM3ntdkMH3(({dC`O&r6`SYASoqTS|)PrnI;&9{q)ovTOxfjAYL3%ow8IH^!(V5 zdj5(bXX%v#(>ZCiW@9fs-@#z%&{4c~N)b$uE>%W{X91D+N#qYhn{1uZOS!e|>SMPv zpPUO$NoM7_ld-!(mSi$nx)ib*s?uw<8X>{4A0GOCzn-nKy(vPW(GXs1VcYc*q_0;c z*nd9Rb1TxsF{#tVsEdj$D*B-+TUy1EO;I*2Sj^#R z=5cV0FXfW&oAYsOtK)|Q9M|0e?h+~Rx>af3nCm%PQdYz7`yo9oQrD`|vgVvBU1rvf z7sc4K$xgFQ8%nP0Sc)olh@)wuzTR$&x`VM;Hf>YXY_n{bs#dn!Y6`K{%F7q5o4!3v zw#vlXq1KM0n~q5oQE_zYZ)g>1Jsbid6i*sMS$nsnP**iK4W z-A;A`ajMdV*7<48loOe|IDwa=ocZVEtH&7ih{xJcnN`|rwMpc6;t>wXW|yvs$8Pk@ z@}dTMSEZ!x_uck_9fO)qQO@GMQ+b+k+QN;k1G;mdod%W(l9?2zMP^8s0o3jkq<92c7p$Z}i&2s`As z*nB{i;{rg~A;-n$1F{?!0KyJAE;b*K<+uP4cF1wD`G73P1%R+aj*HC)WH~MXgdK8R zY(5~%aRDIgkmF+W0a=a<0AYt57n={ra$EoiJ7nT2%-^yl9(}cTMBkzP^v2h3(D!cz zdwaiy(D|zfJ@^QrzyG1%zali05&G>uLe}R9z2tv(@5kE+U4MV4xp_GL`Sg5w7{{k8fuN`-gg~6EtdKy$@q3ealPsm_(n_S1wutt$JFzFN)xMF-1^Yl zKS&PRZ`w}KFH<+@u=1!M^4^5hZ;wLioUladup`fJl>Yeo+mhtDjncbTTWyEy?AY5p zkJ#S%_P%p|;?&&I?dEcQWOIW)OQbw>80kV~ynhxlWtYXlAadBoDZiAPi>^NL zy0gi-;FM-AJ$E+pE|H~~T$U|`e1_`$TJ80S(IklWgP_;USJ}=4p|rj(z1*gb=chz*y}FfH5CiynoZ z(1ULtmnQT|F2%kDAJ?(FLDZ*7)9ceCriA`cU70l&dQO*=y&m*}h@Tc~8g*q+b3v6Y zGkeRA6Y4u`tJUNUWzTbMv)ZYeIx}Tvs=93I-HKc_OiS*t8m(1Toz=z=+wG!!&bk#i zgLJEmtzB+S4XSl5!;n{9oyw*`b-6>UrmUK^il*vDw??bk{BY}ne9ro<$m3;>_6mK{ zv;U_`>IE_XGxBbybA%AHk*$y&Essi=Cl+F`4c zIsUhEU>dfjO$yTgGzYWw>l{=6h`CK=a#@px$7$NGR{I`p>s+{xJnqw$@4<_ua8kkN zOJ_ZOc(8fd2=@U+V2j1fk5@J z8HQPu7E)trK3jz+=d5(*t^B#1|0GbRzX|55>h#WYod>gPx=vT%g@XVf;t+9(`G73q z0zkwe;u7-#S;Pf^h(p9B<^!^b3jh&^h)c`|WDyqtA`TIkm=DMzE&xOvA}%o>kVRYo zh&V)CVm=^?xBw7wh`7XjKo)TUAmR{liTQvm;sQX#A>tDA0a?TafQUoHCFTRNhzkG_ zhloqe2V@Z!03r?%mzWR8A}#<#93n0;ACN^{0Ejq5Tw*>Ti?{#~afrCYd_Wd)0U+WK zaf$hWEaCz{#3AAm^8s1J1%QY{#3kkfvWN=+5r;xt%d@v^na^LX9rAZ*rRRS9n7@B3 zIh(s}Le5_zCFIw8gxH@D@_g{o-5>7o_jw0ft+oBp&%b@Yw8WM7 zrH5boo3EvZ_(1|l00|%gB!C2v01`j~NZ=X>eD`35{4^j-PY%DimD+7>Y`4C6{oeh* E06EB-U;qFB literal 0 HcmV?d00001 diff --git a/examples/SynchronousConcurrency/Default-736h@3x.png b/examples/SynchronousConcurrency/Default-736h@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8949cae16a4217842bd8ad813422c6b02e7cafa GIT binary patch literal 23380 zcmeI3&2QsG7{;dvp`~agBn}`rU}c2_5{^Co*u*CBswBI#5^1Zpi0)~3Y)@Ki6WiGC zChY|TCvFHXE5u>%NL;wV0fEGo^J@PC5E5KCaDuU&4|kFdg zdhMfNZ$I1by=i;VuulBQrS}<*{ybMEgw+Y z?`=z+D4~*BH)T)7hSad?*u+K?zba`e))iG(ur6cGRxKNw(&STfR@qT2@%#2p_u6DQ z7PV`KSr*%hG8&EQBfTCa2MV?4Y7lsEkRh;JT_T6Zzgu6CWjm;?#Ukp#wUkVU{u-UaE@^ zqby1fqcet_rOzCg%}K8}8++;b4u?yJPP41G8G;GYrOI^gIHt-DO{1g4qgQXUOS!b{ z>a(CfpPW-pdFIS>r{mxZS)M6n#Zo9|sKu_;?j)3CQL-0B1E*YN+f#&6rz5@GBVG{Z zNMC6weE<1m&#h>eWYl4c(U7q!V`EQKZH#RestsFJD<)-6&Z8IkLH~G(hhf@Aqv}!V z$$PNP%ca`4;^TXEKT3uqbAll`ph_Gbw3K;crRQu(*_~(*CG51Qqqmf0%@tL# z%OtV!#5ekuMW}3fo+cYjqbZZ7=E~GCvFmv%we)@gvDd507p%LH zca(3HiM7wHU9)NG4c*OMb=lB-OLlervW%Ms#tqVRUEP{mSL6%UTS>sm92r#lFw}aGvc-8^S+s2F7KLn=zH_>DnivE{L5fL|(tNwMYt#KUt6;MNm1~M^YZEUo zWsaBc2I{wzQ?2vUnkgr;U~vM^N4fN`$j=^QbVx(dhAOR!UT2%6Q9m1zgsvU1HSw1l zy|g^7;k{c*UiSyVzc33ax&2^sKn+c6P*;_G^K!n@JzsX48kQ}r_EkzOqv5;LIsT_} zU}&~ED@gy*9L(3RcSynm>O0ExvZf7>(zKng_C46vIdva-)Tgc7gQrX3w1O{|&Q|{L zV6(EzN&qR!9d0QLZSw_F_TSIT=isR5-_TU{QE>i$BCV!*>25)XkQ{H}i_^U`z-5-GJRH)BFa2HG_>+sQA=U>Gio()6`~F zT1ic$<#bgZor~I8wz3Cv_M1SN{U}%{tFv3r!#tQ@)5CP-ykHOxh&TjXVm@3JaB)Dy zA>b18;j(~>10oIqmzWQi1za2uaR|7?e7G#&;(&-lz$NCxWdRolL>vMxF&{1qxHur< z5O9h4a9O~`0TG9QOU#GM0xk}SI0Rf`K3o=XaX`c&;1cuUvVe;NA`StUm=Bi)TpSQ_ z2)M+2xGdn}fQUoDCFa9r0T%~E90D#eA1({HI3VH>aEbYFS-`~s5r=?F%!kVYE)Iw| z1YBZ1To!O~K*S;767%7*fQthn4gr^#50?d891w9R#I-tq&6bAj-P#d*iT2)B=M(k< zuH>!n^bk6E38D8sKf00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!= l00AHX1c1Q*gn;t`y7MJkdHVCOto({Mu5Na}c>U)4e*&NtopJyG literal 0 HcmV?d00001 diff --git a/examples/SynchronousConcurrency/Podfile b/examples/SynchronousConcurrency/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/SynchronousConcurrency/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj b/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..78472428dc --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,361 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */; }; + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */; settings = {ASSET_TAGS = (); }; }; + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncTableViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncTableViewController.m; sourceTree = ""; }; + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncViewController.h; sourceTree = ""; }; + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncViewController.m; sourceTree = ""; }; + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = ""; }; + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; sourceTree = ""; }; + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */, + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */, + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */, + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */, + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */, + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */, + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */, + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..1e14aa0329 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..d98549fd35 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/SynchronousConcurrency/Sample/AppDelegate.h b/examples/SynchronousConcurrency/Sample/AppDelegate.h new file mode 100644 index 0000000000..85855277e9 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AppDelegate.h @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#define UseAutomaticLayout 1 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/SynchronousConcurrency/Sample/AppDelegate.m b/examples/SynchronousConcurrency/Sample/AppDelegate.m new file mode 100644 index 0000000000..a99a682823 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AppDelegate.m @@ -0,0 +1,33 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "AppDelegate.h" + +#import "AsyncTableViewController.h" +#import "AsyncViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + + UITabBarController *tabBarController = [[UITabBarController alloc] initWithNibName:nil bundle:nil]; + self.window.rootViewController = tabBarController; + + [tabBarController setViewControllers:@[[[AsyncTableViewController alloc] init], [[AsyncViewController alloc] init]]]; + + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h new file mode 100644 index 0000000000..a1edcd7f6a --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h @@ -0,0 +1,16 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface AsyncTableViewController : UIViewController + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m new file mode 100644 index 0000000000..090805234c --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m @@ -0,0 +1,91 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#import "AsyncTableViewController.h" +#import "RandomCoreGraphicsNode.h" + +@interface AsyncTableViewController () +{ + ASTableView *_tableView; +} + +@end + +@implementation AsyncTableViewController + +#pragma mark - +#pragma mark UIViewController. + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.asyncDataSource = self; + _tableView.asyncDelegate = self; + + ASRangeTuningParameters tuningParameters; + tuningParameters.leadingBufferScreenfuls = 0.5; + tuningParameters.trailingBufferScreenfuls = 1.0; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypePreload]; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + + self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFeatured tag:0]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo + target:self + action:@selector(reloadEverything)]; + + return self; +} + +- (void)reloadEverything +{ + [_tableView reloadData]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_tableView]; +} + +- (void)viewWillLayoutSubviews +{ + _tableView.frame = self.view.bounds; +} + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +#pragma mark - +#pragma mark ASTableView. + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; + elementNode.preferredFrameSize = CGSizeMake(320, 100); + return elementNode; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return 100; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncViewController.h b/examples/SynchronousConcurrency/Sample/AsyncViewController.h new file mode 100644 index 0000000000..b449abbb2f --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncViewController.h @@ -0,0 +1,13 @@ +// +// AsyncViewController.h +// Sample +// +// Created by Scott Goodson on 9/26/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASViewController.h" + +@interface AsyncViewController : ASViewController + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncViewController.m b/examples/SynchronousConcurrency/Sample/AsyncViewController.m new file mode 100644 index 0000000000..16e8fdb017 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncViewController.m @@ -0,0 +1,37 @@ +// +// AsyncViewController.m +// Sample +// +// Created by Scott Goodson on 9/26/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "AsyncViewController.h" +#import "RandomCoreGraphicsNode.h" + +@implementation AsyncViewController + +- (instancetype)init +{ + if (!(self = [super initWithNode:[[RandomCoreGraphicsNode alloc] init]])) { + return nil; + } + + self.neverShowPlaceholders = YES; + self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFavorites tag:0]; + return self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + // FIXME: This is only being called on the first time the UITabBarController shows us. + [super viewWillAppear:animated]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [self.node recursivelyClearContents]; + [super viewDidDisappear:animated]; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/Info.plist b/examples/SynchronousConcurrency/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h new file mode 100644 index 0000000000..cecc3446b9 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h @@ -0,0 +1,16 @@ +// +// RandomCoreGraphicsNode.h +// Sample +// +// Created by Scott Goodson on 9/5/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +@interface RandomCoreGraphicsNode : ASCellNode +{ + ASTextNode *_textNode; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m new file mode 100644 index 0000000000..046a71e22e --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m @@ -0,0 +1,93 @@ +// +// RandomCoreGraphicsNode.m +// Sample +// +// Created by Scott Goodson on 9/5/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "RandomCoreGraphicsNode.h" +#import + +@implementation RandomCoreGraphicsNode + ++ (UIColor *)randomColor +{ + CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0 + CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white + CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black + return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1]; +} + ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing +{ + CGFloat locations[3]; + NSMutableArray *colors = [NSMutableArray arrayWithCapacity:3]; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[0] = 0.0; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[1] = 1.0; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[2] = ( arc4random() % 256 / 256.0 ); + + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations); + + CGGradientDrawingOptions drawingOptions; + CGContextDrawLinearGradient(ctx, gradient, CGPointZero, CGPointMake(bounds.size.width, bounds.size.height), drawingOptions); + + CGColorSpaceRelease(colorSpace); +} + +- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer +{ + return [self description]; +} + +- (NSDictionary *)textStyle +{ + UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:36.0f]; + + NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + style.paragraphSpacing = 0.5 * font.lineHeight; + style.hyphenationFactor = 1.0; + + return @{ NSFontAttributeName: font, + NSParagraphStyleAttributeName: style }; +} + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _textNode = [[ASTextNode alloc] init]; + _textNode.placeholderEnabled = NO; + _textNode.attributedString = [[NSAttributedString alloc] initWithString:@"Hello, ASDK!" + attributes:[self textStyle]]; + [self addSubnode:_textNode]; + + return self; +} + +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + [_textNode measure:constrainedSize]; + return CGSizeMake(constrainedSize.width, 100); +} + +- (void)layout +{ + CGSize boundsSize = self.bounds.size; + CGSize textSize = _textNode.calculatedSize; + CGRect textRect = CGRectMake(roundf((boundsSize.width - textSize.width) / 2.0), + roundf((boundsSize.height - textSize.height) / 2.0), + textSize.width, + textSize.height); + _textNode.frame = textRect; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/main.m b/examples/SynchronousConcurrency/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} From 22fba5a108ee783f3ab97af6bbe1c203dd97d5aa Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 1 Oct 2015 11:13:49 +0300 Subject: [PATCH 24/39] Revert "Use getter and setter of other properties in ASDisplayNode's frame setter." This reverts commit 93e53bbd87077c1a058dd80ae587f5bff2b36047. --- .../Private/ASDisplayNode+UIViewBridge.mm | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 4490654258..ef013a97c2 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -143,12 +143,22 @@ ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); #endif - CGPoint origin = self.bounds.origin; - CGPoint anchorPoint = self.anchorPoint; + BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); - self.bounds = (CGRect){ origin, rect.size }; - self.position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, - rect.origin.y + rect.size.height * anchorPoint.y); + CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin); + CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); + + CGRect bounds = (CGRect){ origin, rect.size }; + CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x, + rect.origin.y + rect.size.height * anchorPoint.y); + + if (useLayer) { + _layer.bounds = bounds; + _layer.position = position; + } else { + self.bounds = bounds; + self.position = position; + } } - (void)setNeedsDisplay From c90708a25bda807bd3c59a7a116e8a60ac04fb99 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 2 Oct 2015 09:30:49 -0700 Subject: [PATCH 25/39] Corner radius and clipsToBounds should be captured outside block --- AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index ba247db343..98b1c2b6e6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -94,6 +94,8 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // Capture these outside the display block so they are retained. UIColor *backgroundColor = self.backgroundColor; CGRect bounds = self.bounds; + CGFloat cornerRadius = self.cornerRadius; + BOOL clipsToBounds = self.clipsToBounds; CGRect frame; @@ -129,7 +131,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); //support cornerRadius - if (rasterizingFromAscendent && self.cornerRadius && self.clipsToBounds) { + if (rasterizingFromAscendent && cornerRadius && clipsToBounds) { [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:self.cornerRadius] addClip]; } From de1f9788f35b067ca70ab83784c5fa8c59b4bf3d Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Oct 2015 12:44:21 -0700 Subject: [PATCH 26/39] Simplify creation of Photos Framework NSURLs in ASMultiplexImageNode --- AsyncDisplayKit/ASMultiplexImageNode.h | 35 ++++++++++++-- AsyncDisplayKit/ASMultiplexImageNode.mm | 46 +++++++++++++------ .../Details/ASPhotosFrameworkImageRequest.h | 11 ----- .../Details/ASPhotosFrameworkImageRequest.m | 9 ---- .../ASPhotosFrameworkImageRequestTests.m | 3 +- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index 5b811f6654..2a3ec7f821 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -9,6 +9,7 @@ #import #import +#import @protocol ASMultiplexImageNodeDelegate; @protocol ASMultiplexImageNodeDataSource; @@ -98,6 +99,13 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { */ @property (nonatomic, readonly) id displayedImageIdentifier; +/** + * @abstract The image manager that this image node should use when requesting images from the Photos framework. Defaults to `PHImageManager.defaultManager`. + + * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. + */ +@property (nonatomic, strong) PHImageManager *imageManager; + @end @@ -195,11 +203,32 @@ didFinishDownloadingImageWithIdentifier:(id)imageIdentifier * @abstract An image URL for the specified identifier. * @param imageNode The sender. * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include assets-library, URLs converted from ASPhotosFrameworkImageRequest, HTTP, HTTPS, and FTP URLs. If the - * image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource + * @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note). + * + * If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource * multiplexImageNode:imageForImageIdentifier:]> instead. - * @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available. + * @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available. + * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. */ - (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier; @end + +#pragma mark - + +@interface NSURL (ASPhotosFrameworkURLs) + +/** + * @abstract Create an NSURL that specifies an image from the Photos framework. + * + * @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL + * created by this method and the image node will attempt to load the image from the Photos framework. + * @note The `synchronous` flag in `options` is ignored. + * @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`. + */ ++ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier + targetSize:(CGSize)targetSize + contentMode:(PHImageContentMode)contentMode + options:(PHImageRequestOptions *)options; + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 026c1680a6..aa65649a7f 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -157,7 +157,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; - + _imageManager = PHImageManager.defaultManager; + return self; } @@ -455,7 +456,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. - else if (ASPhotosFrameworkImageRequest *request = nextImageURL.asyncdisplaykit_photosRequest) { + else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier); finishedLoadingBlock(image, nextImageIdentifier, error); @@ -529,25 +530,27 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent PHAsset *imageAsset = [assetFetchResult firstObject]; PHImageRequestOptions *options = [request.options copy]; + + // We don't support opportunistic delivery – one request, one image. + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { + options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; + } + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { // Without this flag the result will be delivered on the main queue, which is pointless // But synchronous -> HighQualityFormat so we only use it if high quality format is specified options.synchronous = YES; } - [[PHImageManager defaultManager] requestImageForAsset:imageAsset - targetSize:request.targetSize - contentMode:request.contentMode - options:options - resultHandler:^(UIImage *image, NSDictionary *info) { - if (NSThread.isMainThread) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - completionBlock(image, info[PHImageErrorKey]); - }); - } else { - completionBlock(image, info[PHImageErrorKey]); - } - }]; + [self.imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { + if (NSThread.isMainThread) { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + completionBlock(image, info[PHImageErrorKey]); + }); + } else { + completionBlock(image, info[PHImageErrorKey]); + } + }]; }); } @@ -648,3 +651,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } @end + +@implementation NSURL (ASPhotosFrameworkURLs) + ++ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options +{ + ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; + request.options = options; + request.contentMode = contentMode; + request.targetSize = targetSize; + return request.url; +} + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h index 4f55b0c401..7630fe2e84 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -48,8 +48,6 @@ extern NSString *const ASPhotosURLScheme; @discussion Some properties of this object are ignored when converting this request into a URL. As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - Note that ASMultiplexImageNode does not support PHImageRequestOptionsDeliveryModeOpportunistic - because it sends multiple images, and that mode will be replaced by PHImageRequestOptionsDeliveryModeHighQualityFormat */ @property (nonatomic, strong) PHImageRequestOptions *options; @@ -65,13 +63,4 @@ extern NSString *const ASPhotosURLScheme; @end -@interface NSURL (ASPhotosRequestConverting) - -/** - @abstract A convenience function that calls `[ASPhotosFrameworkImageRequest requestWithURL:self]`. - */ -- (/*nullable*/ ASPhotosFrameworkImageRequest *)asyncdisplaykit_photosRequest; - -@end - // NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m index dcf47957c6..0e4b4e66ac 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -158,12 +158,3 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; } @end - -@implementation NSURL (ASPhotosRequestConverting) - -- (ASPhotosFrameworkImageRequest *)asyncdisplaykit_photosRequest -{ - return [ASPhotosFrameworkImageRequest requestWithURL:self]; -} - -@end diff --git a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m index 5c276706b4..0d34e1944e 100644 --- a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m +++ b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m @@ -46,7 +46,8 @@ static NSString *const kTestAssetID = @"testAssetID"; - (void)testThatParsingFromURLWorks { - XCTAssertEqualObjects([self.class urlForExampleImageRequest].asyncdisplaykit_photosRequest, [self.class exampleImageRequest]); + NSURL *url = [self.class urlForExampleImageRequest]; + XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]); } - (void)testThatCopyingWorks From ac8b224d9bdde160e109f0782ad66005b2655689 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Oct 2015 13:04:48 -0700 Subject: [PATCH 27/39] Fix broken ASPhotosFrameworkImageRequest test --- AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m index 0d34e1944e..d6c641bd8a 100644 --- a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m +++ b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m @@ -33,7 +33,7 @@ static NSString *const kTestAssetID = @"testAssetID"; + (NSURL *)urlForExampleImageRequest { - NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID]; + NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID]; return [NSURL URLWithString:str]; } From d5a6ad21235483381d80bbdeb151af808dd25e60 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Oct 2015 15:41:01 -0700 Subject: [PATCH 28/39] Avoid accessing PHImageManager in ASMultiplexImageNode init --- AsyncDisplayKit/ASMultiplexImageNode.h | 2 +- AsyncDisplayKit/ASMultiplexImageNode.mm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index 2a3ec7f821..1956760899 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -100,7 +100,7 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { @property (nonatomic, readonly) id displayedImageIdentifier; /** - * @abstract The image manager that this image node should use when requesting images from the Photos framework. Defaults to `PHImageManager.defaultManager`. + * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. */ diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 057cf279af..4ffb8a1275 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -157,7 +157,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; - _imageManager = PHImageManager.defaultManager; self.shouldBypassEnsureDisplay = YES; return self; @@ -543,7 +542,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent options.synchronous = YES; } - [self.imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { + PHImageManager *imageManager = self.imageManager ?: PHImageManager.defaultManager; + [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { if (NSThread.isMainThread) { dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ completionBlock(image, info[PHImageErrorKey]); From 304ec9b543deaec9e3dc0fe99bc4fdceb509e097 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Oct 2015 15:51:32 -0700 Subject: [PATCH 29/39] Refuse to parse ph:// URLs if iOS < 8 for safety --- AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m index 0e4b4e66ac..d46b3791c1 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -8,6 +8,7 @@ #import "ASPhotosFrameworkImageRequest.h" #import "ASBaseDefines.h" +#import "ASAvailability.h" NSString *const ASPhotosURLScheme = @"ph"; @@ -100,8 +101,8 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; + (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url { - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { + // not a photos URL or iOS < 8 + if (![url.scheme isEqualToString:ASPhotosURLScheme] || !AS_AT_LEAST_IOS8) { return nil; } From 45a555cdb532a097716f3aaf40ba2c2451b5b442 Mon Sep 17 00:00:00 2001 From: ricky cancro Date: Fri, 2 Oct 2015 15:56:31 -0700 Subject: [PATCH 30/39] Static layout bug? I noticed when creating a static layout that the sizeRange of the children was being ignored. The case was: I had an image as a child of a static layout The image was set to have an exact range of 90x90 When the static layout was measured, the constrained size came in as 375xInf (the width of the containing node and unbounded) The static layout computed its final size as 375x90 According to the comments in component kit's header file the static layout "[c]omputes a size that is the union of all childrens' frames." It appeared that we are only doing that in the unbounded direction. My fix is to do it in both directions. --- AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index 8e8435c9cc..7652247f9b 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -58,18 +58,14 @@ [sublayouts addObject:sublayout]; } - if (isnan(size.width) || size.width >= FLT_MAX - FLT_EPSILON) { - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } + size.width = constrainedSize.min.width; + for (ASLayout *sublayout in sublayouts) { + size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); } - if (isnan(size.height) || size.height >= FLT_MAX - FLT_EPSILON) { - size.height = constrainedSize.min.height; - for (ASLayout *sublayout in sublayouts) { - size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); - } + size.height = constrainedSize.min.height; + for (ASLayout *sublayout in sublayouts) { + size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); } return [ASLayout layoutWithLayoutableObject:self From eb5e670e5da2584d2d050d5a002c2a746cfcc9dc Mon Sep 17 00:00:00 2001 From: ricky cancro Date: Fri, 2 Oct 2015 20:58:52 -0700 Subject: [PATCH 31/39] wasn't copying alignSelf --- AsyncDisplayKit/Layout/ASLayoutOptions.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.mm b/AsyncDisplayKit/Layout/ASLayoutOptions.mm index e96f955c21..a40eea2bd5 100644 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.mm +++ b/AsyncDisplayKit/Layout/ASLayoutOptions.mm @@ -98,6 +98,7 @@ static Class gDefaultLayoutOptionsClass = nil; self.spacingBefore = layoutOptions.spacingBefore; self.flexGrow = layoutOptions.flexGrow; self.flexShrink = layoutOptions.flexShrink; + self.alignSelf = layoutOptions.alignSelf; self.ascender = layoutOptions.ascender; self.descender = layoutOptions.descender; From 4380e0baae4b4d6caa3e29c8a616b7108c2282c4 Mon Sep 17 00:00:00 2001 From: Roy Marmelstein Date: Sun, 4 Oct 2015 10:58:33 +0200 Subject: [PATCH 32/39] Framework test target --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 + ObjectiveC.gcno | Bin 0 -> 924 bytes QuartzCore.gcno | Bin 0 -> 924 bytes build.sh | 13 + smoke-tests/Framework/Default-568h@2x.png | Bin 0 -> 17520 bytes smoke-tests/Framework/Default-667h@2x.png | Bin 0 -> 18314 bytes smoke-tests/Framework/Default-736h@3x.png | Bin 0 -> 23380 bytes .../Sample.xcodeproj/project.pbxproj | 409 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/Sample.xcscheme | 88 ++++ .../Framework/Sample/AppDelegate.swift | 28 ++ .../Sample/AsyncDisplayKit-Bridging-Header.h | 12 + smoke-tests/Framework/Sample/Info.plist | 36 ++ .../Framework/Sample/ViewController.swift | 66 +++ 14 files changed, 661 insertions(+) create mode 100644 ObjectiveC.gcno create mode 100644 QuartzCore.gcno create mode 100644 smoke-tests/Framework/Default-568h@2x.png create mode 100644 smoke-tests/Framework/Default-667h@2x.png create mode 100644 smoke-tests/Framework/Default-736h@3x.png create mode 100644 smoke-tests/Framework/Sample.xcodeproj/project.pbxproj create mode 100644 smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme create mode 100644 smoke-tests/Framework/Sample/AppDelegate.swift create mode 100644 smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h create mode 100644 smoke-tests/Framework/Sample/Info.plist create mode 100644 smoke-tests/Framework/Sample/ViewController.swift diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3cf1189c0b..1a58fabfe9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; @@ -1612,6 +1613,7 @@ B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */, B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */, 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */, + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */, diff --git a/ObjectiveC.gcno b/ObjectiveC.gcno new file mode 100644 index 0000000000000000000000000000000000000000..05bc771aa773306a2dac55e20902adbb6fb7cbe7 GIT binary patch literal 924 zcmd1LOHS7^GB9a6bMz1c0|TQBkN|-zatj`?18MDO$Kc@PqWqkku*}qQg`C97)FS7c z#Ny&u1_lA3ynbkLYEiL%ZemexYEDsprM_cvWnQvNW^n;fsdr`xCLd~?Uw%reUM_Npb<{_;@1& jY+)6jl#`#F9iN$3mYNpb<{_;@1& jY+)6jl#`#F9iN$3mYd4j{zA$_fDzj(<0aNm5GErIkpxYKyiNhuw)iPOK)jv7ILE zX$2=vNSqMjuzvyn01^l!&fGb11%VKUJ#d1togeN?GIZ5NXr+nN#&6zy-^_g8%(HXw zC;NNvt`}Y|AcWR;cUlJs-FSe|v%$?9wB^z7;a&RmT(ENzA@uwU$=@3K;>(v1dh1j7 z=r}%Zzh_$hpoDF|LrUX8Kzk!p+Z+ejIwi5tAzjz27ytPB+oIsw_2ONlEw_Uv>A5=> zAvwI*JF+fLt*TwzY!qr^lLi=&7z^V;-;2y~y_m)|>2=a96@|1*d|EGVB?Ah_?R}x? zheR++hG@x(E|jY!#ZdH$@}{85iYCdLq-dg|nsU`t3}NyqHVX8r7TS(^(7HVtj#~9% zFOCCKl18IZX{43>uq!FmYE_a|NmWJKLyXS77>`9Sx|Ic)#%Ynr3f&-feNRZ@;*LLz z>&0R+(Cjl=*C3b;&fF^pra5W1XJap&?_jWW=qTPkqX;HQmnx&9^MFVPB=U!$MYhlAQf@66Bj z|2*0AP^5)p(x97B7Zq7l^dnU^wW=wXMY(LsaxO^L6U1oyw(FcPiJ@0aSV{SoJJ@kVxmu}Iuwgikil?D-@`ccMLYI~~+|Tt%X8*WD zED5tP)Q%g`&3T^Nv~=sHh3t@gc)4}z=(}k_3UC;a_F@aa3oW;+{SC2935s!7CvQTNS)j=ZPiAOjjTV1**{`H_| zAunrS?3$F+{l5DjWFf+5*UNz+E2@mEy0~GKv8b12B32ERh~*YY# zOjVy2r2i=g^Zje}u-7GaLz*sY+VE7R?Mk)pnrX}5N1pbyWoPhmDV$dD#nM?z9v)0u zc4-N~X}MEs(n8yO!({!Blk(Xgrv1yROh1y6{GUkkZYVurOY?3hoh#q>+_*t+dFez! zb54~!;beEB3SG^ek1L%$16wZRDjn z*F>6SEBEA_md>n|K9|#<1k&s`!9rD?-Fcqm#e7XqGAfA02LV9XCdb9*1H2p;0Kzso zE;b+F<+uP4w#jj^`2a7+1%R+kj*HC)csVWrgl%$MY(ButaRDG~ljCCZ0bY&^0AZUP z7n=|8a$Eoi+vK>|e1MnZ0zlX%$HnFYyc`z*!ZtZBHXq>SxBw8g$#Jpy058V{fUr%D zi_HgkIW7Q%ZE{>}KETUy0U&IX<6`pxUXBX@VVfKmn-B1ETmT5$HaRXfAK>M<01&pxak2RTFUJLduuUee!u%~;;?Y-YNAw-q_19i`o4$W5 zSUU%8gf3o1=)uPb{q;Bf{sp0-jL@(55i&nV=tcjtdq3Pp=;jBzt*xW+PrrSqtWo9n z4mH~~(86W|0m0Z!mC3H;oB2JPKLYv26X|1**9Ztu0edguN}{{Z>Z BpTqzF literal 0 HcmV?d00001 diff --git a/smoke-tests/Framework/Default-667h@2x.png b/smoke-tests/Framework/Default-667h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..988ea56bab508432a9b5edf5b4003b8d6eb28469 GIT binary patch literal 18314 zcmeI3O>g5w7{{k8fuN`-4j@ieR!AV>_&sU7NtUJQ(n@40-6Go4?!+Fq)+V;GohI!G z?VdOxaRDJDE}Y@S35k~j-+&X}0EFPoX&Kvjai63^QH#(@Poy?}=9&M0?BC2hb`Jh< z=gymJt1qn*Le{poI=h5i`-70DYR_EbEt}tb`40bjHrhH!2zl=L>~Dp9_W6s1-1x}f z+fVnqZ`n>bs8ToVvFdmb@!o_qZ;m7CJYcERV~2jws{Hxw_Z7)^Tb1{$uG)<@*pa_= z8ne5nclMmq2TsGS+-$El$2K<@u#`&UK|e_BajTO1+Wa_cRw`27C4JDU+{y-&_PaaM zMi?{6s#>z6YNk|gR5eR8*R|IqUDXUlH5AQ|HQiPlwq{9_Po=%ekD9UT*}I+Fli_&O zsvME(dCV{6`m(D} zeQ!QFXPmOkx#!Nt{yDNNQ^@kAP|Q%1U#qhPQMa|4tk!K+EtwQOfk!uVeeYz6hq-R6X4#|E z6MFE?Za^@)UjN%$8@!pL+p0T@&)To z=nS*1GO>2KH8rT_S%x9kT%F3ML3O!D*Ev+PQP))6@EUY|YF&~qSic?nyv)&lsZVzH z|8|HaHVZ>-`3YZ~C#6Npmu_Cj?y~pKwoVg$KQBlTjT4q_w^n7k-)0qTR-iIXzHM#l zWTmX_WV?a6P327M{Iq7uiOgS|z>85X{7drl#~2;4K(wI>tGw6wCQ0a}BO0??hkQ-^ z<3=wm&uXA|Q_1Rn-~SJ0A)&{YoC8O7P2JOFPg8rcS+}U%t2s>8y!Cor)xGr|`<4Ic??p$df*8#U4Cc3a1r(v2-@G zhXkVRYoh&V)CVm=^?xBw7wh`7XjKo)TUAmR{liTQvm;sQX#A>tDA0a?TafQUoH zCFTRNhzkG_hloqe2V@Z!03r?%mzWR8A}#<#93n0;ACN^{0Ejq5Tw*>Ti?{#~afrCY zd_Wd)0U+WKaf$hWEaCz{#3AAm^8s1J1%QY{#3kkfvWN=+5r>FN%m-u<7XTs-5to<` z$RaKPL>wY6F&~gcTmXnTL|kG%Ad9#F5OFBPwK{*xmIeIP+7W+;_LKe>4uAhva<+E6 zgq*%i$On%I`Rg(N{*{oSO2}{b39&yV=9YP+yL&(Lmix07u-ml+(%YGh>H}+FP9(yAHTp%BQ^b{d4 zy&vrEWqaLMEiW3>9Y1nueKd$!Z$esEMzQ1FqM6{*gCK0ze);0dnh^Ny+UrJF?#Ao% zFxWUv=#A5@UGMakXZp1(_DXAHu>b>_Il^er4^wN@t`%`DcAhs&HKFK|-D=m?@`S=( zcUxGG5-J#VL-b@t7aC?=F%-S2yeO!$qDitQDVnILmTX#zA&ifjy~55~iQlt!I@iYO znANTwW?5`W(r`Ge54C!f97u|3nv$$asw%P`VtNv0&PWW?%Vm&BoDNOBB#5&h3WYqb z<3>kWyH?8+O^@-o2Jtjen2y;oiqgo5C8aJ)%S666)x<|hzqmBtlW3m~Xqcr;r!3XQ zhfx-#htWAHr|xqTvpMN@r@5D2?_jVrb(&p0W(dYemnzfUlbA|7G>wiDk6t}yL%FkX37mqmMcNZo*+gy@`K*Vk{Ei^l2@xS zDm`Hgwt0LfbM7iiwQnVk?^(Sl8913844eZh#o<9qnl)B>PM4Im9`&PyT^6;qmb9$7 zqAHe|wHc2L*HbvcUI-=X~ba7RuYE#vkYLC(aVz*nB zFNE$y-ci0)#-V){+BKSn*3ev0Z|a(;tG+3^nzkw`O+G_o%CRh1oWQ%IT*j}+&(ASDq#Mzp`z3D~VN&2IpTs3xcGd47jXd~d=qi;`2a8C0zmjC;^OlGUc?1}@J+On$ zco7!>!Z#5YpAYaNE&zmYA}&53;6+>j2;W3pd_KU7xBw8oiMaTDfERHAAbb;X@%aER z;sQYUCgS4r0baxffbdPk#peUOhzkJWn~00g2Y3+|0KzvB7oQLCA}#=gZz3)}AK*n? z00`eiTzo#ji?{#~zKOW_e1I2m0U&%6aq;;8FX93~_$C+E%Iq~;8nSn5hwLTV?>2t; zguQ?(cpE!iLQbC}qD`eDj?@0Z>MqJOBUy literal 0 HcmV?d00001 diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ac95942b7a --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,409 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7319D22E19004363C2 /* AppDelegate.swift */; }; + 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7519D22E19004363C2 /* ViewController.swift */; }; + 05DDD8DB19D2336300013C30 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */; }; + 34566CAA1BC1204100715E6B /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */; }; + 34566CAB1BC1204100715E6B /* AsyncDisplayKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053D919EE266A00E385DE /* Default-667h@2x.png */; }; + 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 34566CA21BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09AC195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; + 34566CA41BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09BC195D04C000B7D73C; + remoteInfo = AsyncDisplayKitTests; + }; + 34566CA61BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; + remoteInfo = AsyncDisplayKitTestHost; + }; + 34566CA81BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B35061DA1B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + 34566CAC1BC1204100715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B35061D91B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 34566CAE1BC1204100715E6B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 34566CAB1BC1204100715E6B /* AsyncDisplayKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 050E7C6E19D22E19004363C2 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 050E7C7219D22E19004363C2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 050E7C7319D22E19004363C2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 050E7C7519D22E19004363C2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05DDD8DC19D2341D00013C30 /* AsyncDisplayKit-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Bridging-Header.h"; sourceTree = ""; }; + 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = ../../AsyncDisplayKit.xcodeproj; sourceTree = ""; }; + 34566CAF1BC1208200715E6B /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 34566CB11BC1208700715E6B /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; + 6C5053D919EE266A00E385DE /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 050E7C6B19D22E19004363C2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 34566CAA1BC1204100715E6B /* AsyncDisplayKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 050E7C6519D22E19004363C2 = { + isa = PBXGroup; + children = ( + 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */, + 050E7C7019D22E19004363C2 /* Sample */, + 050E7C6F19D22E19004363C2 /* Products */, + 092C2001FE124604891D6E90 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 050E7C6F19D22E19004363C2 /* Products */ = { + isa = PBXGroup; + children = ( + 050E7C6E19D22E19004363C2 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 050E7C7019D22E19004363C2 /* Sample */ = { + isa = PBXGroup; + children = ( + 050E7C7319D22E19004363C2 /* AppDelegate.swift */, + 050E7C7519D22E19004363C2 /* ViewController.swift */, + 050E7C7119D22E19004363C2 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 050E7C7119D22E19004363C2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 05DDD8DC19D2341D00013C30 /* AsyncDisplayKit-Bridging-Header.h */, + 050E7C7219D22E19004363C2 /* Info.plist */, + 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */, + 6C5053D919EE266A00E385DE /* Default-667h@2x.png */, + 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 092C2001FE124604891D6E90 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 34566CB11BC1208700715E6B /* AssetsLibrary.framework */, + 34566CAF1BC1208200715E6B /* Photos.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 34566C9C1BC1202A00715E6B /* Products */ = { + isa = PBXGroup; + children = ( + 34566CA31BC1202A00715E6B /* libAsyncDisplayKit.a */, + 34566CA51BC1202A00715E6B /* AsyncDisplayKitTests.xctest */, + 34566CA71BC1202A00715E6B /* AsyncDisplayKitTestHost.app */, + 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 050E7C6D19D22E19004363C2 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 050E7C8D19D22E1A004363C2 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 050E7C6A19D22E19004363C2 /* Sources */, + 050E7C6B19D22E19004363C2 /* Frameworks */, + 050E7C6C19D22E19004363C2 /* Resources */, + 34566CAE1BC1204100715E6B /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 34566CAD1BC1204100715E6B /* PBXTargetDependency */, + ); + name = Sample; + productName = Sample; + productReference = 050E7C6E19D22E19004363C2 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 050E7C6619D22E19004363C2 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 050E7C6D19D22E19004363C2 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 050E7C6919D22E19004363C2 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 050E7C6519D22E19004363C2; + productRefGroup = 050E7C6F19D22E19004363C2 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 34566C9C1BC1202A00715E6B /* Products */; + ProjectRef = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 050E7C6D19D22E19004363C2 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 34566CA31BC1202A00715E6B /* libAsyncDisplayKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libAsyncDisplayKit.a; + remoteRef = 34566CA21BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA51BC1202A00715E6B /* AsyncDisplayKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = AsyncDisplayKitTests.xctest; + remoteRef = 34566CA41BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA71BC1202A00715E6B /* AsyncDisplayKitTestHost.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = AsyncDisplayKitTestHost.app; + remoteRef = 34566CA61BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; + remoteRef = 34566CA81BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 050E7C6C19D22E19004363C2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05DDD8DB19D2336300013C30 /* Default-568h@2x.png in Resources */, + 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */, + 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 050E7C6A19D22E19004363C2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */, + 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 34566CAD1BC1204100715E6B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "AsyncDisplayKit-iOS"; + targetProxy = 34566CAC1BC1204100715E6B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 050E7C8B19D22E1A004363C2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 050E7C8C19D22E1A004363C2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 050E7C8E19D22E1A004363C2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Sample/AsyncDisplayKit-Bridging-Header.h"; + }; + name = Debug; + }; + 050E7C8F19D22E1A004363C2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Sample/AsyncDisplayKit-Bridging-Header.h"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 050E7C6919D22E19004363C2 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 050E7C8B19D22E1A004363C2 /* Debug */, + 050E7C8C19D22E1A004363C2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 050E7C8D19D22E1A004363C2 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 050E7C8E19D22E1A004363C2 /* Debug */, + 050E7C8F19D22E1A004363C2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 050E7C6619D22E19004363C2 /* Project object */; +} diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..8d7f73e325 --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/smoke-tests/Framework/Sample/AppDelegate.swift b/smoke-tests/Framework/Sample/AppDelegate.swift new file mode 100644 index 0000000000..a2b00b1727 --- /dev/null +++ b/smoke-tests/Framework/Sample/AppDelegate.swift @@ -0,0 +1,28 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + let window = UIWindow(frame: UIScreen.mainScreen().bounds) + window.backgroundColor = UIColor.whiteColor() + window.rootViewController = ViewController(nibName: nil, bundle: nil) + window.makeKeyAndVisible() + self.window = window + return true + } + +} diff --git a/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h b/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h new file mode 100644 index 0000000000..e5488e4ee6 --- /dev/null +++ b/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h @@ -0,0 +1,12 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import diff --git a/smoke-tests/Framework/Sample/Info.plist b/smoke-tests/Framework/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/smoke-tests/Framework/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/smoke-tests/Framework/Sample/ViewController.swift b/smoke-tests/Framework/Sample/ViewController.swift new file mode 100644 index 0000000000..9801a20523 --- /dev/null +++ b/smoke-tests/Framework/Sample/ViewController.swift @@ -0,0 +1,66 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate { + + var tableView: ASTableView + + + // MARK: UIViewController. + + override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + self.tableView = ASTableView() + + super.init(nibName: nil, bundle: nil) + + self.tableView.asyncDataSource = self + self.tableView.asyncDelegate = self + } + + required init(coder aDecoder: NSCoder) { + fatalError("storyboards are incompatible with truth and beauty") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.addSubview(self.tableView) + } + + override func viewWillLayoutSubviews() { + self.tableView.frame = self.view.bounds + } + + override func prefersStatusBarHidden() -> Bool { + return true + } + + + // MARK: ASTableView data source and delegate. + + func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! { + let patter = NSString(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) + let node = ASTextCellNode() + node.text = patter as String + + return node + } + + func numberOfSectionsInTableView(tableView: UITableView!) -> Int { + return 1 + } + + func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { + return 20 + } + +} From caf306123cd8cf18184b087ed54edd6b72312fc2 Mon Sep 17 00:00:00 2001 From: Roy Marmelstein Date: Sun, 4 Oct 2015 11:00:03 +0200 Subject: [PATCH 33/39] Removed unnecessary bridging header --- .../xcschemes/AsyncDisplayKit-iOS.xcscheme | 19 +++++++++++-------- .../xcschemes/AsyncDisplayKit.xcscheme | 13 ++++++++----- .../Sample.xcodeproj/project.pbxproj | 11 ++++++----- .../xcshareddata/xcschemes/Sample.xcscheme | 13 ++++++++----- .../Sample/AsyncDisplayKit-Bridging-Header.h | 12 ------------ smoke-tests/Framework/Sample/Info.plist | 2 +- .../Framework/Sample/ViewController.swift | 1 + 7 files changed, 35 insertions(+), 36 deletions(-) delete mode 100644 smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index 2a1226a3f6..df6da9c539 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ @@ -23,26 +23,29 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> + + @@ -52,15 +55,15 @@ diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 3eaf6c9402..c37db8e85f 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:AsyncDisplayKit.xcodeproj"> + + + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,15 +38,18 @@ ReferencedContainer = "container:Sample.xcodeproj"> + + @@ -62,10 +65,10 @@ diff --git a/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h b/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h deleted file mode 100644 index e5488e4ee6..0000000000 --- a/smoke-tests/Framework/Sample/AsyncDisplayKit-Bridging-Header.h +++ /dev/null @@ -1,12 +0,0 @@ -/* This file provided by Facebook is for non-commercial testing and evaluation - * purposes only. Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#import diff --git a/smoke-tests/Framework/Sample/Info.plist b/smoke-tests/Framework/Sample/Info.plist index 35d842827b..fb4115c84c 100644 --- a/smoke-tests/Framework/Sample/Info.plist +++ b/smoke-tests/Framework/Sample/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/smoke-tests/Framework/Sample/ViewController.swift b/smoke-tests/Framework/Sample/ViewController.swift index 9801a20523..43db9fb420 100644 --- a/smoke-tests/Framework/Sample/ViewController.swift +++ b/smoke-tests/Framework/Sample/ViewController.swift @@ -10,6 +10,7 @@ */ import UIKit +import AsyncDisplayKit class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate { From f4ba447159295a4d50e8551b614a2297431f0420 Mon Sep 17 00:00:00 2001 From: Roy Marmelstein Date: Sun, 4 Oct 2015 11:03:52 +0200 Subject: [PATCH 34/39] Updating Travis and Build script to test dynamic framework building --- .travis.yml | 1 + build.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e4c1005c7..5392503db1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - MODE=tests - MODE=examples - MODE=life-without-cocoapods + - MODE=framework script: ./build.sh $MODE after_success: - slather diff --git a/build.sh b/build.sh index 482efadac9..fe5ae7b191 100755 --- a/build.sh +++ b/build.sh @@ -64,8 +64,8 @@ if [ "$MODE" = "framework" ]; then echo "Verifying that AsyncDisplayKit functions as a dynamic framework (for Swift/Carthage users)." xctool \ - -workspace "smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcworkspace" \ - -scheme "Life Without CocoaPods" \ + -project "smoke-tests/Framework/Sample.xcodeproj" \ + -scheme Sample \ -sdk "$SDK" \ -destination "$PLATFORM" \ build From c92a15515db770d9dad7046033e5baeb99e82a14 Mon Sep 17 00:00:00 2001 From: Roy Marmelstein Date: Sun, 4 Oct 2015 11:07:47 +0200 Subject: [PATCH 35/39] Adding readme --- smoke-tests/Framework/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 smoke-tests/Framework/README.md diff --git a/smoke-tests/Framework/README.md b/smoke-tests/Framework/README.md new file mode 100644 index 0000000000..cce2cb22fb --- /dev/null +++ b/smoke-tests/Framework/README.md @@ -0,0 +1,11 @@ +# "Framework" + +This is a very simple pseudo-"integration test" project that links against +AsyncDisplayKit as a dynamic framework, for Swift/Carthage users. + +If it fails to compile, Travis CI builds will fail. To escape from such dire straits: + +* If you've added a new class intended for public use, make sure you added its + header to the "Public" group of the "Headers" build phase in the + AsyncDisplayKit-iOS framework target. Note that this smoke test will only fail + if you remembered to add your new file to the umbrella helper. From 61bb4f022e0dbfd846e4361a0e156bbe1eee5f92 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 4 Oct 2015 11:15:57 -0700 Subject: [PATCH 36/39] Improved messages for thread affinity and frame transform identity assertions. --- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 4 ++-- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 167c94de7d..6d853721ca 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -463,5 +463,5 @@ @end -#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity") -#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity") +#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") +#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index ef013a97c2..977c98ec40 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -60,7 +60,7 @@ #define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector] /** - * This category implements certainly frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, + * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. * This allows text sizing in -calculateSizeThatFits: (essentially a simplified layout) to happen off the main thread * without any CALayer or UIView actually existing while still being able to set and read properties from ASDisplayNode instances. @@ -122,7 +122,7 @@ // Frame is only defined when transform is identity. #if DEBUG // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); + ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif CGPoint position = self.position; @@ -140,7 +140,7 @@ // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform #if DEBUG // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); + ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); From 4601fccb5a0172c8a80ad480c5ff8e39789e5103 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 5 Oct 2015 11:37:29 -0700 Subject: [PATCH 37/39] Optimize ASDataController._layoutNodes --- AsyncDisplayKit/Details/ASDataController.mm | 26 +++++++++------------ 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 693f1653cc..651f048c6b 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -105,31 +105,27 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } dispatch_group_t layoutGroup = dispatch_group_create(); - + ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodes.count); for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { - NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))]; + NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - //TODO: There should be a fast-path that avoids all of this object creation. - NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor]; - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; - [nodeBoundSizes addObject:[NSValue valueWithBytes:&constrainedSize objCType:@encode(ASSizeRange)]]; - }]; + for (NSUInteger k = j; k < j + batchCount; k++) { + nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]]; + } dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - ASCellNode *node = nodes[j + idx]; - ASSizeRange constrainedSize; - [nodeBoundSizes[idx] getValue:&constrainedSize]; + for (NSUInteger k = j; k < j + batchCount; k++) { + ASCellNode *node = nodes[k]; + ASSizeRange constrainedSize = nodeBoundSizes[k - j]; [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); - }]; + node.frame.size = node.calculatedSize; + } }); } // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); - + free(nodeBoundSizes); // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } From 33c07db58c1ce213d8e823a8801be424861e320e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 5 Oct 2015 11:42:25 -0700 Subject: [PATCH 38/39] Set node.frame entirely --- AsyncDisplayKit/Details/ASDataController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 651f048c6b..eecfe43adc 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -118,7 +118,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASCellNode *node = nodes[k]; ASSizeRange constrainedSize = nodeBoundSizes[k - j]; [node measureWithSizeRange:constrainedSize]; - node.frame.size = node.calculatedSize; + node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height); } }); } From 99b9f73b1aa042c89f4cc0e6a28594e0b50ae497 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 5 Oct 2015 11:51:05 -0700 Subject: [PATCH 39/39] Fix index into size ranges --- AsyncDisplayKit/Details/ASDataController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index eecfe43adc..7b07ff76ef 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -116,7 +116,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSUInteger k = j; k < j + batchCount; k++) { ASCellNode *node = nodes[k]; - ASSizeRange constrainedSize = nodeBoundSizes[k - j]; + ASSizeRange constrainedSize = nodeBoundSizes[k]; [node measureWithSizeRange:constrainedSize]; node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height); }