diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index eb23732cb7..bc0797ac86 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -221,32 +221,6 @@ */ - (void)replaceSubnode:(ASDisplayNode *)subnode withSubnode:(ASDisplayNode *)replacementSubnode; -/** - * @abstract Add a subnode, but have it size asynchronously on a background queue. - * - * @param subnode The unsized subnode to insert into the view hierarchy - * @param completion The completion callback will be called on the main queue after the subnode has been inserted in - * place of the placeholder. - * - * @return A placeholder node is inserted into the hierarchy where the node will be. The placeholder can be moved around - * in the hiercharchy while the view is sizing. Once sizing is complete on the background queue, this placeholder will - * be removed and the replacement will be put at its place. - */ -- (ASDisplayNode *)addSubnodeAsynchronously:(ASDisplayNode *)subnode - completion:(void(^)(ASDisplayNode *replacement))completion; - -/** - * @abstract Replace a subnode, but have it size asynchronously on a background queue. - * - * @param subnode A subnode of self. - * @param replacementSubnode A node with which to replace subnode. - * @param completion The completion callback will be called on the main queue after the replacementSubnode has replaced - * subnode. - */ -- (void)replaceSubnodeAsynchronously:(ASDisplayNode *)subnode - withNode:(ASDisplayNode *)replacementSubnode - completion:(void(^)(BOOL cancelled, ASDisplayNode *replacement, ASDisplayNode *oldSubnode))completion; - /** * @abstract Remove this node from its supernode. * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 8b5565ba40..d5b0556a66 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1401,91 +1401,6 @@ - (void)_enqueueAsyncSizingWithSentinel:(ASSentinel *)sentinel completion:(void( } -- (void)replaceSubnodeAsynchronously:(ASDisplayNode *)old withNode:(ASDisplayNode *)replacement completion:(void(^)(BOOL cancelled, ASDisplayNode *replacement, ASDisplayNode *oldSubnode))completion -{ - - ASDisplayNodeAssert(old.supernode == self, @"Must replace something that is actually a subnode. You passed: %@", old); - ASDisplayNodeAssert(!replacement.nodeLoaded, @"Can't async size something that already has a view, since we currently have no way to convert a viewed node into a viewless one..."); - - // If we're already marked for replacement, cancel the pending request - ASSentinel *sentinel = [old _asyncReplaceSentinel]; - uint32_t sentinelValue = [sentinel increment]; - - // Enqueue async sizing on our argument - [replacement _enqueueAsyncSizingWithSentinel:sentinel completion:^(ASDisplayNode *replacementCompletedNode) { - // Sizing is done; swap with our other view - // Check sentinel one more time in case it changed during sizing - if (replacementCompletedNode && sentinel.value == sentinelValue) { - if (old.supernode) { - if (old.supernode.inWindow) { - // Now wait for async display before notifying delegate that replacement is complete - - // When async sizing is complete, add subnode below placeholder with 0 alpha - CGFloat replacementAlpha = replacement.alpha; - BOOL wasAsyncTransactionContainer = replacement.asyncdisplaykit_asyncTransactionContainer; - [old.supernode insertSubnode:replacement belowSubnode:old]; - - replacementCompletedNode.alpha = 0.0; - replacementCompletedNode.asyncdisplaykit_asyncTransactionContainer = YES; - - ASDisplayNodeCAssert(replacementCompletedNode.nodeLoaded, @".layer shouldn't be the thing to load the view"); - - [replacement.layer.asyncdisplaykit_asyncTransaction addCompletionBlock:^(id unused, BOOL canceled) { - ASDisplayNodeCAssertMainThread(); - - canceled |= (sentinel.value != sentinelValue); - - replacementCompletedNode.alpha = replacementAlpha; - replacementCompletedNode.asyncdisplaykit_asyncTransactionContainer = wasAsyncTransactionContainer; - - if (!canceled) { - [old removeFromSupernode]; - } else { - [replacementCompletedNode removeFromSupernode]; - } - - completion(canceled, replacementCompletedNode, old); - }]; - } else { - // Not in window, don't wait for async display - [old.supernode replaceSubnode:old withSubnode:replacement]; - completion(NO, replacementCompletedNode, old); - } - - } else { // Old has been moved no longer to be in the hierarchy - // TODO: add code to removeFromSupernode and hook UIView methods to cancel sentinel here? - @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Tried to replaceSubnodeAsynchronously an ASDisplayNode, but then removed it from the hierarchy... what did you mean?" userInfo:nil]; - - completion(NO, replacementCompletedNode, old); - } - } else { // If we were cancelled - completion(YES, nil, nil); - } - }]; - - -} - -- (ASDisplayNode *)addSubnodeAsynchronously:(ASDisplayNode *)replacement completion:(void(^)(ASDisplayNode *fullySizedSubnode))completion -{ - ASDisplayNodeAssertThreadAffinity(self); - - // Create a placeholder ASDisplayNode that saves this guy's place in the view hierarchy for when things return later - ASDisplayNode *placeholder = [[ASDisplayNode alloc] init]; - - [self addSubnode:placeholder]; - [self replaceSubnodeAsynchronously:placeholder withNode:replacement completion:^(BOOL cancelled, ASDisplayNode *replacementBlock, ASDisplayNode *placeholderBlock) { - if (replacementBlock && placeholderBlock && !cancelled) { - [placeholderBlock removeFromSupernode]; - completion(replacementBlock); - } else { - [placeholderBlock removeFromSupernode]; - } - }]; - - return placeholder; -} - @end @implementation ASDisplayNode (Debugging) diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 4bb760126c..d7475a4878 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -914,167 +914,6 @@ - (void)checkReplaceSubnodeWithView:(BOOL)loaded layerBacked:(BOOL)isLayerBacked [d release]; } -- (void)testAddSubnodeAsync -{ - - ASTestDisplayNode *parent = [[ASTestDisplayNode alloc] init]; - - __block BOOL calculateSizeCalled = NO; - __block CGSize passedInSize; - __block BOOL sizeCalculatedOnMainThread; - ASTestDisplayNode *nodeToAddAsync = [[ASTestDisplayNode alloc] init]; - nodeToAddAsync.bounds = CGRectMake(100, 40, 42, 56); - nodeToAddAsync.calculateSizeBlock = ^(ASDisplayNode *n, CGSize size){ - calculateSizeCalled = YES; - passedInSize = size; - sizeCalculatedOnMainThread = [NSThread isMainThread]; - return CGSizeZero; - }; - - dispatch_suspend([ASDisplayNode asyncSizingQueue]); - - __block BOOL completed = NO; - ASDisplayNode *placeholder = [parent addSubnodeAsynchronously:nodeToAddAsync completion:^(ASDisplayNode *replacement) { - completed = YES; - }]; - - // Check it hasn't been added yet - XCTAssertTrue(placeholder.supernode == parent, @"didn't make a placeholder"); - XCTAssertTrue(nodeToAddAsync.supernode == nil, @"oops, added node too soon"); - XCTAssertFalse(calculateSizeCalled, @"too soon to calculate size!"); - - dispatch_resume([ASDisplayNode asyncSizingQueue]); - - // Let the block execute on the bg thread - XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return completed; }), @"Didn't finish the test fast enough"); - - XCTAssertTrue(placeholder.supernode == nil, @"didn't remove the placeholder"); - XCTAssertTrue(nodeToAddAsync.supernode == parent, @"oops, didn't add the node"); - XCTAssertTrue(calculateSizeCalled, @"too soon to calculate size!"); - XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(42, 56), passedInSize), @"Should pass in initial bounds size"); - XCTAssertFalse(sizeCalculatedOnMainThread, @"Should pass in initial bounds size"); - - [parent release]; - [nodeToAddAsync release]; -} - -- (void)testReplaceSubnodeAsync -{ - ASTestDisplayNode *parent = [[ASTestDisplayNode alloc] init]; - - __block BOOL calculateSizeCalled = NO; - __block CGSize passedInSize; - __block BOOL sizeCalculatedOnMainThread; - ASTestDisplayNode *nodeToAddAsync = [[ASTestDisplayNode alloc] init]; - nodeToAddAsync.bounds = CGRectMake(100, 40, 42, 56); - nodeToAddAsync.calculateSizeBlock = ^(ASDisplayNode *n, CGSize size){ - calculateSizeCalled = YES; - passedInSize = size; - sizeCalculatedOnMainThread = [NSThread isMainThread]; - return CGSizeZero; - }; - - ASDisplayNode *replaceMe = [[ASDisplayNode alloc] init]; - [parent addSubnode:replaceMe]; - - dispatch_suspend([ASDisplayNode asyncSizingQueue]); - - __block BOOL completed = NO; - - [parent replaceSubnodeAsynchronously:replaceMe withNode:nodeToAddAsync completion:^(BOOL cancelled, ASDisplayNode *nodeToAddAsyncBlockArgument, ASDisplayNode *replaceMeBlockArgument) { - XCTAssertTrue(nodeToAddAsyncBlockArgument == nodeToAddAsync, @"Passed in wrong node for the replacing node"); - XCTAssertTrue(replaceMeBlockArgument == replaceMe, @"Passed in wrong node for the replaced node"); - completed = YES; - }]; - - // Check it hasn't been replaced yet - XCTAssertTrue(replaceMe.supernode == parent, @"removed too soon!"); - XCTAssertTrue(nodeToAddAsync.supernode == nil, @"oops, added node too soon"); - XCTAssertFalse(calculateSizeCalled, @"too soon to calculate size!"); - - dispatch_resume([ASDisplayNode asyncSizingQueue]); - - // Let the block execute on the bg thread - XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return completed; }), @"Didn't finish the test fast enough"); - - XCTAssertTrue(replaceMe.supernode == nil, @"didn't remove old node"); - XCTAssertTrue(nodeToAddAsync.supernode == parent, @"oops, added node too soon"); - XCTAssertTrue(calculateSizeCalled, @"too soon to calculate size!"); - XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(42, 56), passedInSize), @"Should pass in initial bounds size"); - XCTAssertFalse(sizeCalculatedOnMainThread, @"Should pass in initial bounds size"); - - [parent release]; - [nodeToAddAsync release]; -} - -- (void)testCancelReplaceSubnodeAsyncByReplacingAgain -{ - - ASTestDisplayNode *parent = [[ASTestDisplayNode alloc] init]; - - dispatch_semaphore_t allowFirst = dispatch_semaphore_create(0); - ASTestDisplayNode *first = [[ASTestDisplayNode alloc] init]; - first.bounds = CGRectMake(100, 40, 42, 56); - first.calculateSizeBlock = ^(ASDisplayNode *n, CGSize size){ - dispatch_semaphore_wait(allowFirst, DISPATCH_TIME_FOREVER); - return CGSizeZero; - }; - - dispatch_semaphore_t allowSecond = dispatch_semaphore_create(0); - ASTestDisplayNode *second = [[ASTestDisplayNode alloc] init]; - second.bounds = CGRectMake(100, 40, 42, 56); - second.calculateSizeBlock = ^(ASDisplayNode *n, CGSize size){ - dispatch_semaphore_wait(allowSecond, DISPATCH_TIME_FOREVER); - return CGSizeMake(10, 20); - }; - - - ASDisplayNode *replaceMe = [[ASDisplayNode alloc] init]; - [parent addSubnode:replaceMe]; - - __block BOOL firstFinished = NO; - [parent replaceSubnodeAsynchronously:replaceMe withNode:first completion:^(BOOL cancelled, ASDisplayNode *firstBlockArgument, ASDisplayNode *replaceMeBlockArgument) { - XCTAssertNil(firstBlockArgument, @"Should have cancelled"); - XCTAssertNil(replaceMeBlockArgument, @"Should have cancelled"); - firstFinished = YES; - }]; - - __block BOOL secondFinished = NO; - [parent replaceSubnodeAsynchronously:replaceMe withNode:second completion:^(BOOL cancelled, ASDisplayNode *secondBlockArgument, ASDisplayNode *replaceMeBlockArgument) { - XCTAssertNotNil(secondBlockArgument, @"Should have the relevant node passed in"); - XCTAssertNotNil(replaceMeBlockArgument, @"Should have the relevant node passed in"); - secondFinished = YES; - }]; - - XCTAssertTrue(replaceMe.supernode == parent, @"didn't remove old node"); - XCTAssertTrue(first.supernode == nil, @"oops, added node too soon"); - XCTAssertTrue(second.supernode == nil, @"oops, added node too soon"); - XCTAssertFalse(first.nodeLoaded, @"first view loaded too soon"); - XCTAssertFalse(second.nodeLoaded, @"second view loaded too soon"); - - // Allow first to complete, but verify that the nodes are nil, indicating cancellation (asserts are in blocks above) - dispatch_semaphore_signal(allowFirst); - - // Let the work execute on the bg thread - ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return firstFinished; }); - - XCTAssertTrue(first.supernode == nil, @"first should not be added to the hierarchy ever"); - XCTAssertTrue(second.supernode == nil, @"second should not yet be added to the hierarchy"); - - dispatch_semaphore_signal(allowSecond); - ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return secondFinished; }); - - XCTAssertTrue(first.supernode == nil, @"first should not be added to the hierarchy ever"); - XCTAssertTrue(second.supernode == parent, @"second should be added now, woot!"); - XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(10, 20), [second calculatedSize]), @"Should have correct calculatedSize"); - - [parent release]; - [first release]; - [second release]; - dispatch_release(allowFirst); - dispatch_release(allowSecond); -} - - (void)testInsertSubnodeAtIndexView { [self checkInsertSubnodeAtIndexWithViewLoaded:YES layerBacked:NO];