Merge pull request #1605 from lappp9/override-callback-for-display-range

[ASDisplayNode] Added callbacks for entering and exiting fetch data and display ranges
This commit is contained in:
appleguy 2016-06-11 22:21:20 -07:00 committed by GitHub
commit a7128cd213
13 changed files with 140 additions and 25 deletions

View File

@ -214,9 +214,9 @@
// To be overriden by subclasses // To be overriden by subclasses
} }
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
CGRect cellFrame = CGRectZero; CGRect cellFrame = CGRectZero;
if (_scrollView) { if (_scrollView) {

View File

@ -173,9 +173,9 @@
} }
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible); NSLog(@"%@ - visible: %d", self, isVisible);
} }
#endif #endif

View File

@ -242,6 +242,31 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER; - (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called whenever the visiblity of the node changed.
*
* @discussion Subclasses may use this to monitor when they become visible.
*/
- (void)visibleStateDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called whenever the the node has entered or exited the display state.
*
* @discussion Subclasses may use this to monitor when a node should be rendering its content.
*
* @note This method can be called from any thread and should therefore be thread safe.
*/
- (void)displayStateDidChange:(BOOL)inDisplayState ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called whenever the the node has entered or left the load state.
*
* @discussion Subclasses may use this to monitor data for a node should be loaded, either from a local or remote source.
*
* @note This method can be called from any thread and should therefore be thread safe.
*/
- (void)loadStateDidChange:(BOOL)inLoadState ASDISPLAYNODE_REQUIRES_SUPER;
/** /**
* Called just before the view is added to a window. * Called just before the view is added to a window.
*/ */

View File

@ -2044,6 +2044,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
} }
#pragma mark Hierarchy State
- (void)willEnterHierarchy - (void)willEnterHierarchy
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -2064,7 +2066,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
if (![self supportsRangeManagedInterfaceState]) { if (![self supportsRangeManagedInterfaceState]) {
self.interfaceState = ASInterfaceStateNone; self.interfaceState = ASInterfaceStateNone;
} else { } else {
// This case is important when tearing down hierarchies. We must deliver a visibilityDidChange:NO callback, as part our API guarantee that this method can be used for // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for
// things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
// Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
// same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
@ -2083,6 +2085,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
} }
} }
#pragma mark Interface State
- (void)clearContents - (void)clearContents
{ {
// No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released.
@ -2131,7 +2135,22 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibilityDidChange:(BOOL)isVisible
{ {
// subclass override // subclass override
}
- (void)visibleStateDidChange:(BOOL)isVisible
{
// subclass override
}
- (void)displayStateDidChange:(BOOL)inDisplayState
{
//subclass override
}
- (void)loadStateDidChange:(BOOL)inLoadState
{
//subclass override
} }
/** /**
@ -2178,10 +2197,12 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
if (nowFetchData != wasFetchData) { if (nowFetchData != wasFetchData) {
if (nowFetchData) { if (nowFetchData) {
[self fetchData]; [self fetchData];
[self loadStateDidChange:YES];
} else { } else {
if ([self supportsRangeManagedInterfaceState]) { if ([self supportsRangeManagedInterfaceState]) {
[self clearFetchedData]; [self clearFetchedData];
} }
[self loadStateDidChange:NO];
} }
} }
@ -2225,6 +2246,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
} }
} }
} }
[self displayStateDidChange:nowDisplay];
} }
// Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel
@ -2233,14 +2256,16 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState);
if (nowVisible != wasVisible) { if (nowVisible != wasVisible) {
[self visibilityDidChange:nowVisible]; [self visibleStateDidChange:nowVisible];
[self visibilityDidChange:nowVisible]; //TODO: remove once this method has been deprecated
} }
[self interfaceStateDidChange:newState fromState:oldState]; [self interfaceStateDidChange:newState fromState:oldState];
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{ {
// subclass hook
} }
- (void)enterInterfaceState:(ASInterfaceState)interfaceState - (void)enterInterfaceState:(ASInterfaceState)interfaceState

View File

@ -158,9 +158,9 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSDefaultRunLoopMode;
[self.animatedImage clearAnimatedImageCache]; [self.animatedImage clearAnimatedImageCache];
} }
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (isVisible) { if (isVisible) {

View File

@ -295,11 +295,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
} }
} }
/* visibilityDidChange in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary /* visibileStateDidChange in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary
in ASNetworkImageNode as well. */ in ASNetworkImageNode as well. */
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
if (_downloaderImplementsSetPriority) { if (_downloaderImplementsSetPriority) {
ASDN::MutexLocker l(_downloadIdentifierLock); ASDN::MutexLocker l(_downloadIdentifierLock);

View File

@ -278,11 +278,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
} }
} }
/* visibilityDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary /* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
in ASMultiplexImageNode as well. */ in ASMultiplexImageNode as well. */
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
if (_downloaderImplementsSetPriority) { if (_downloaderImplementsSetPriority) {
_lock.lock(); _lock.lock();

View File

@ -145,9 +145,9 @@
} }
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
NSLog(@"%@ - visible: %d", self, isVisible); NSLog(@"%@ - visible: %d", self, isVisible);
} }
#endif #endif

View File

@ -382,9 +382,9 @@ static NSString * const kStatus = @"status";
} }
} }
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);

View File

@ -163,9 +163,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
} }
} }
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibleStateDidChange:isVisible];
ASDN::MutexLocker l(_videoPlayerLock); ASDN::MutexLocker l(_videoPlayerLock);

View File

@ -95,7 +95,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
{ {
_scrollDirection = scrollDirection; _scrollDirection = scrollDirection;
// Perform update immediately, so that cells receive a visibilityDidChange: call before their first pixel is visible. // Perform update immediately, so that cells receive a visibleStateDidChange: call before their first pixel is visible.
[self scheduleRangeUpdate]; [self scheduleRangeUpdate];
} }

View File

@ -86,6 +86,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@property (atomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node); @property (atomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node);
@property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); @property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size);
@property (atomic) BOOL hasFetchedData; @property (atomic) BOOL hasFetchedData;
@property (atomic) BOOL displayRangeStateChangedToYES;
@property (atomic) BOOL displayRangeStateChangedToNO;
@property (atomic) BOOL loadStateChangedToYES;
@property (atomic) BOOL loadStateChangedToNO;
@end @end
@interface ASTestResponderNode : ASTestDisplayNode @interface ASTestResponderNode : ASTestDisplayNode
@ -110,6 +116,28 @@ for (ASDisplayNode *n in @[ nodes ]) {\
self.hasFetchedData = NO; self.hasFetchedData = NO;
} }
- (void)displayStateDidChange:(BOOL)inDisplayState
{
[super displayStateDidChange:inDisplayState];
if (inDisplayState) {
self.displayRangeStateChangedToYES = YES;
} else {
self.displayRangeStateChangedToNO = YES;
}
}
- (void)loadStateDidChange:(BOOL)inLoadState
{
[super loadStateDidChange:inLoadState];
if (inLoadState) {
self.loadStateChangedToYES = YES;
} else {
self.loadStateChangedToNO = YES;
}
}
- (void)dealloc - (void)dealloc
{ {
if (_willDeallocBlock) { if (_willDeallocBlock) {
@ -1878,4 +1906,42 @@ static bool stringContainsPointer(NSString *description, const void *p) {
XCTAssert(node.bounds.size.height == 8, @"Wrong ASDisplayNode.bounds.size.height"); XCTAssert(node.bounds.size.height == 8, @"Wrong ASDisplayNode.bounds.size.height");
} }
- (void)testDidEnterDisplayIsCalledWhenNodesEnterDisplayRange
{
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
XCTAssert([node displayRangeStateChangedToYES]);
}
- (void)testDidExitDisplayIsCalledWhenNodesExitDisplayRange
{
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
XCTAssert([node displayRangeStateChangedToNO]);
}
- (void)testDidEnterFetchDataIsCalledWhenNodesEnterFetchDataRange
{
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
XCTAssert([node loadStateChangedToYES]);
}
- (void)testDidExitFetchDataIsCalledWhenNodesExitFetchDataRange
{
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
XCTAssert([node loadStateChangedToNO]);
}
@end @end

View File

@ -223,12 +223,11 @@
}]; }];
_videoNode.playerNode.layer.frame = CGRectZero; _videoNode.playerNode.layer.frame = CGRectZero;
[_videoNode visibilityDidChange:YES]; [_videoNode visibleStateDidChange:YES];
XCTAssertTrue(_videoNode.shouldBePlaying); XCTAssertTrue(_videoNode.shouldBePlaying);
} }
- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater - (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater
{ {
_videoNode.asset = _firstAsset; _videoNode.asset = _firstAsset;