2611 rename fetch data (#2689)

* Replace fetch data with preload terminology
- Deprecate `-fetchData` and `-clearFetchedData` in favor of `-preload` and `-clearPreloadedData`
- Move `-setNeedsPreload`, `-recursivelyPreload` and `-recursivelyClearPreloadedData` to ASDisplayNode+FrameworkPrivate.h
- Update internal implementation, comments and tests

* Folllow up on #2642:
- Remove -preload and -clearPreloadedData in favor of -didEnterPreloadState and -didExitPreloadState.
- -didEnterPreloadState and -didExitPreloadState call the deprecated -fetchData and -clearFetchedData methods if they are overriden.

* Missed one in a test

* Get rid of behavior change for now.

* Revert more behavior changes, fix tests.

* Don't need these anymore.
This commit is contained in:
Garrett Moon
2016-12-01 13:41:22 -08:00
committed by Hannah Troisi
parent c7ea15a5e4
commit ba2268ac99
20 changed files with 151 additions and 167 deletions

View File

@@ -167,10 +167,10 @@
[self.rangeController clearContents]; [self.rangeController clearContents];
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
[self.rangeController clearFetchedData]; [self.rangeController clearPreloadedData];
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState

View File

@@ -1681,18 +1681,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_nextLayoutInvalidationStyle = invalidationStyle; _nextLayoutInvalidationStyle = invalidationStyle;
} }
#pragma mark - Memory Management
- (void)clearContents
{
[_rangeController clearContents];
}
- (void)clearFetchedData
{
[_rangeController clearFetchedData];
}
#pragma mark - _ASDisplayView behavior substitutions #pragma mark - _ASDisplayView behavior substitutions
// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. // Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. // Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.

View File

@@ -115,4 +115,21 @@ ASLayoutElementStyleForwardingDeclaration
*/ */
@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead."); @property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead.");
/**
* @abstract Indicates that the node should fetch any external data, such as images.
*
* @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching
* should be done asynchronously. The node is also responsible for managing the memory of any data.
* The data may be remote and accessed via the network, but could also be a local database query.
*/
- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead.");
/**
* Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node.
*
* @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or
* selectively clear fetched data.
*/
- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead.");
@end @end

View File

@@ -323,23 +323,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy; @property (nonatomic, readonly, assign, getter=isInHierarchy) BOOL inHierarchy;
/**
* @abstract Indicates that the node should fetch any external data, such as images.
*
* @discussion Subclasses may override this method to be notified when they should begin to fetch data. Fetching
* should be done asynchronously. The node is also responsible for managing the memory of any data.
* The data may be remote and accessed via the network, but could also be a local database query.
*/
- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node.
*
* @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearFetchedData or
* selectively clear fetched data.
*/
- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER;
/** /**
* Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers * Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers
* on the current node. * on the current node.

View File

@@ -478,31 +478,6 @@ extern NSInteger const ASDefaultDrawingPriority;
*/ */
- (void)recursivelyClearContents; - (void)recursivelyClearContents;
/**
* @abstract Calls -clearFetchedData on the receiver and its subnode hierarchy.
*
* @discussion Clears any memory-intensive fetched content.
* This method is used to notify the node that it should purge any content that is both expensive to fetch and to
* retain in memory.
*
* @see [ASDisplayNode(Subclassing) clearFetchedData] and [ASDisplayNode(Subclassing) fetchData]
*/
- (void)recursivelyClearFetchedData;
/**
* @abstract Calls -fetchData on the receiver and its subnode hierarchy.
*
* @discussion Fetches content from remote sources for the current node and all subnodes.
*
* @see [ASDisplayNode(Subclassing) fetchData] and [ASDisplayNode(Subclassing) clearFetchedData]
*/
- (void)recursivelyFetchData;
/**
* @abstract Triggers a recursive call to fetchData when the node has an interfaceState of ASInterfaceStatePreload
*/
- (void)setNeedsDataFetch;
/** /**
* @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are * @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are
* displayed. * displayed.

View File

@@ -184,6 +184,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) { if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) {
overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits;
} }
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(fetchData))) {
overrides |= ASDisplayNodeMethodOverrideFetchData;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(clearFetchedData))) {
overrides |= ASDisplayNodeMethodOverrideClearFetchedData;
}
return overrides; return overrides;
} }
@@ -211,7 +217,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead overwrite calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead overwrite calculateLayoutThatFits:.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead overwrite calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead overwrite calculateLayoutThatFits:.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString);
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearPreloadedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString);
} }
// Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values
@@ -2930,34 +2936,24 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}); });
} }
- (void)fetchData - (void)setNeedsPreload
{
// subclass override
}
- (void)setNeedsDataFetch
{ {
if (self.isInPreloadState) { if (self.isInPreloadState) {
[self recursivelyFetchData]; [self recursivelyPreload];
} }
} }
- (void)recursivelyFetchData - (void)recursivelyPreload
{ {
ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) {
[node fetchData]; [node didEnterPreloadState];
}); });
} }
- (void)clearFetchedData - (void)recursivelyClearPreloadedData
{
// subclass override
}
- (void)recursivelyClearFetchedData
{ {
ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) {
[node clearFetchedData]; [node didExitPreloadState];
}); });
} }
@@ -2983,13 +2979,23 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)didEnterPreloadState - (void)didEnterPreloadState
{ {
if (_methodOverrides & ASDisplayNodeMethodOverrideFetchData) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self fetchData]; [self fetchData];
#pragma clang diagnostic pop
}
} }
- (void)didExitPreloadState - (void)didExitPreloadState
{ {
if (_methodOverrides & ASDisplayNodeMethodOverrideClearFetchedData) {
if ([self supportsRangeManagedInterfaceState]) { if ([self supportsRangeManagedInterfaceState]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self clearFetchedData]; [self clearFetchedData];
#pragma clang diagnostic pop
}
} }
} }
@@ -3047,7 +3053,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
// Trigger asynchronous measurement if it is not already cached or being calculated. // Trigger asynchronous measurement if it is not already cached or being calculated.
} }
// For the FetchData and Display ranges, we don't want to call -clear* if not being managed by a range controller. // For the Preload and Display ranges, we don't want to call -clear* if not being managed by a range controller.
// Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop.
// Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition.
@@ -3946,6 +3952,16 @@ ASLayoutElementStyleForwarding
} }
} }
- (void)fetchData
{
// subclass override
}
- (void)clearFetchedData
{
// subclass override
}
- (void)cancelLayoutTransitionsInProgress - (void)cancelLayoutTransitionsInProgress
{ {
[self cancelLayoutTransition]; [self cancelLayoutTransition];

View File

@@ -72,9 +72,9 @@
[super setLayerBacked:layerBacked]; [super setLayerBacked:layerBacked];
} }
- (void)fetchData - (void)didEnterPreloadState
{ {
[super fetchData]; [super didEnterPreloadState];
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
if (self.isLiveMap) { if (self.isLiveMap) {
[self addLiveMap]; [self addLiveMap];
@@ -84,9 +84,9 @@
}); });
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
if (self.isLiveMap) { if (self.isLiveMap) {
[self removeLiveMap]; [self removeLiveMap];

View File

@@ -216,12 +216,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
[super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. [super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful.
[self _setDisplayedImageIdentifier:nil withImage:nil]; [self _setDisplayedImageIdentifier:nil withImage:nil];
// NOTE: We intentionally do not cancel image downloads until `clearFetchedData`. // NOTE: We intentionally do not cancel image downloads until `clearPreloadedData`.
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
[_phImageRequestOperation cancel]; [_phImageRequestOperation cancel];
@@ -236,9 +236,9 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
self.image = nil; self.image = nil;
} }
- (void)fetchData - (void)didEnterPreloadState
{ {
[super fetchData]; [super didEnterPreloadState];
[self _loadImageIdentifiers]; [self _loadImageIdentifiers];
} }
@@ -281,7 +281,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
{ {
[super displayWillStart]; [super displayWillStart];
[self fetchData]; [self didEnterPreloadState];
if (_downloaderImplementsSetPriority) { if (_downloaderImplementsSetPriority) {
{ {
@@ -396,7 +396,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
_imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES]; _imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES];
} }
[self setNeedsDataFetch]; [self setNeedsPreload];
} }
- (void)reloadImageIdentifierSources - (void)reloadImageIdentifierSources

View File

@@ -145,7 +145,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
}); });
} }
[self setNeedsDataFetch]; [self setNeedsPreload];
} }
- (NSURL *)URL - (NSURL *)URL
@@ -265,8 +265,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
} }
} }
// TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -fetchData call. // TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -preload call.
[self fetchData]; [self didEnterPreloadState];
if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@@ -306,9 +306,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
[self _updateProgressImageBlockOnDownloaderIfNeeded]; [self _updateProgressImageBlockOnDownloaderIfNeeded];
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@@ -321,9 +321,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
} }
} }
- (void)fetchData - (void)didEnterPreloadState
{ {
[super fetchData]; [super didEnterPreloadState];
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);

View File

@@ -136,10 +136,10 @@
[self.rangeController clearContents]; [self.rangeController clearContents];
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
[self.rangeController clearFetchedData]; [self.rangeController clearPreloadedData];
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState

View File

@@ -1646,18 +1646,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[super endUpdates]; [super endUpdates];
} }
#pragma mark - Memory Management
- (void)clearContents
{
[_rangeController clearContents];
}
- (void)clearFetchedData
{
[_rangeController clearFetchedData];
}
#pragma mark - Helper Methods #pragma mark - Helper Methods
// Note: This is called every layout, and so it is very performance sensitive. // Note: This is called every layout, and so it is very performance sensitive.

View File

@@ -11,6 +11,7 @@
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASVideoNode.h" #import "ASVideoNode.h"
#import "ASEqualityHelpers.h" #import "ASEqualityHelpers.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
@@ -365,9 +366,9 @@ static NSString * const kRate = @"rate";
} }
} }
- (void)fetchData - (void)didEnterPreloadState
{ {
[super fetchData]; [super didEnterPreloadState];
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
AVAsset *asset = self.asset; AVAsset *asset = self.asset;
@@ -405,9 +406,9 @@ static NSString * const kRate = @"rate";
} }
} }
- (void)clearFetchedData - (void)didExitPreloadState
{ {
[super clearFetchedData]; [super didExitPreloadState];
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@@ -505,10 +506,10 @@ static NSString * const kRate = @"rate";
- (void)_setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL - (void)_setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL
{ {
[self clearFetchedData]; [self didExitPreloadState];
_asset = asset; _asset = asset;
_assetURL = assetURL; _assetURL = assetURL;
[self setNeedsDataFetch]; [self setNeedsPreload];
} }
- (void)setVideoComposition:(AVVideoComposition *)videoComposition - (void)setVideoComposition:(AVVideoComposition *)videoComposition
@@ -617,7 +618,7 @@ static NSString * const kRate = @"rate";
} }
if (_player == nil) { if (_player == nil) {
[self setNeedsDataFetch]; [self setNeedsPreload];
} }
if (_playerNode == nil) { if (_playerNode == nil) {

View File

@@ -55,7 +55,7 @@ typedef void(^ASImageCacherCompletion)(id <ASImageContainerProtocol> _Nullable i
completion:(ASImageCacherCompletion)completion; completion:(ASImageCacherCompletion)completion;
/** /**
@abstract Called during clearFetchedData. Allows the cache to optionally trim items. @abstract Called during clearPreloadedData. Allows the cache to optionally trim items.
@note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful @note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful
if you have a memory and disk cache in which case you'll likely want to clear out the memory cache. if you have a memory and disk cache in which case you'll likely want to clear out the memory cache.
*/ */

View File

@@ -31,8 +31,8 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) {
ASLayoutRangeModeFull, ASLayoutRangeModeFull,
/** /**
* Visible Only mode is used when a range controller should set its display and fetch data regions to only the size of their bounds. * Visible Only mode is used when a range controller should set its display and preload regions to only the size of their bounds.
* This causes all additional backing stores & fetched data to be released, while ensuring a user revisiting the view will * This causes all additional backing stores & preloaded data to be released, while ensuring a user revisiting the view will
* still be able to see the expected content. This mode is automatically set on all ASRangeControllers when the app suspends, * still be able to see the expected content. This mode is automatically set on all ASRangeControllers when the app suspends,
* allowing the operating system to keep the app alive longer and increase the chance it is still warm when the user returns. * allowing the operating system to keep the app alive longer and increase the chance it is still warm when the user returns.
*/ */
@@ -40,7 +40,7 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) {
/** /**
* Low Memory mode is used when a range controller should discard ALL graphics buffers, including for the area that would be visible * Low Memory mode is used when a range controller should discard ALL graphics buffers, including for the area that would be visible
* the next time the user views it (bounds). The only range it preserves is Fetch Data, which is limited to the bounds, allowing * the next time the user views it (bounds). The only range it preserves is Preload, which is limited to the bounds, allowing
* the content to be restored relatively quickly by re-decoding images (the compressed images are ~10% the size of the decoded ones, * the content to be restored relatively quickly by re-decoding images (the compressed images are ~10% the size of the decoded ones,
* and text is a tiny fraction of its rendered size). * and text is a tiny fraction of its rendered size).
*/ */

View File

@@ -69,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
// These methods call the corresponding method on each node, visiting each one that // These methods call the corresponding method on each node, visiting each one that
// the range controller has set a non-default interface state on. // the range controller has set a non-default interface state on.
- (void)clearContents; - (void)clearContents;
- (void)clearFetchedData; - (void)clearPreloadedData;
/** /**
* An object that describes the layout behavior of the ranged component (table view, collection view, etc.) * An object that describes the layout behavior of the ranged component (table view, collection view, etc.)

View File

@@ -250,7 +250,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
// Typically the preloadIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. // Typically the preloadIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
// Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items.
// This means that during iteration, we will first visit visible, then display, then fetch data nodes. // This means that during iteration, we will first visit visible, then display, then preload nodes.
[allIndexPaths unionSet:displayIndexPaths]; [allIndexPaths unionSet:displayIndexPaths];
[allIndexPaths unionSet:preloadIndexPaths]; [allIndexPaths unionSet:preloadIndexPaths];
@@ -292,14 +292,14 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
} }
} else { } else {
// If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
// instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. // instant we come onscreen. So, preload and display all of those things, but don't waste resources preloading yet.
// We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
if ([allCurrentIndexPaths containsObject:indexPath]) { if ([allCurrentIndexPaths containsObject:indexPath]) {
// DO NOT set Visible: even though these elements are in the visible range / "viewport", // DO NOT set Visible: even though these elements are in the visible range / "viewport",
// our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above
// Set Layout, Fetch Data // Set Layout, Preload
interfaceState |= ASInterfaceStatePreload; interfaceState |= ASInterfaceStatePreload;
if (rangeMode != ASLayoutRangeModeLowMemory) { if (rangeMode != ASLayoutRangeModeLowMemory) {
@@ -501,7 +501,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
} }
} }
- (void)clearFetchedData - (void)clearPreloadedData
{ {
for (NSArray *section in [_dataSource completedNodes]) { for (NSArray *section in [_dataSource completedNodes]) {
for (ASDisplayNode *node in section) { for (ASDisplayNode *node in section) {

View File

@@ -156,6 +156,31 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
*/ */
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously; - (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
/**
* @abstract Calls -didExitPreloadState on the receiver and its subnode hierarchy.
*
* @discussion Clears any memory-intensive preloaded content.
* This method is used to notify the node that it should purge any content that is both expensive to fetch and to
* retain in memory.
*
* @see [ASDisplayNode(Subclassing) didExitPreloadState] and [ASDisplayNode(Subclassing) didEnterPreloadState]
*/
- (void)recursivelyClearPreloadedData;
/**
* @abstract Calls -didEnterPreloadState on the receiver and its subnode hierarchy.
*
* @discussion Fetches content from remote sources for the current node and all subnodes.
*
* @see [ASDisplayNode(Subclassing) didEnterPreloadState] and [ASDisplayNode(Subclassing) didExitPreloadState]
*/
- (void)recursivelyPreload;
/**
* @abstract Triggers a recursive call to -didEnterPreloadState when the node has an interfaceState of ASInterfaceStatePreload
*/
- (void)setNeedsPreload;
/** /**
* @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO.
* *

View File

@@ -44,7 +44,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1,
ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2,
ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3,
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4,
ASDisplayNodeMethodOverrideFetchData = 1 << 5,
ASDisplayNodeMethodOverrideClearFetchedData = 1 << 6
}; };
FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification; FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification;

View File

@@ -89,7 +89,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@interface ASTestDisplayNode : ASDisplayNode @interface ASTestDisplayNode : ASDisplayNode
@property (nonatomic, copy) void (^willDeallocBlock)(__unsafe_unretained ASTestDisplayNode *node); @property (nonatomic, copy) void (^willDeallocBlock)(__unsafe_unretained ASTestDisplayNode *node);
@property (nonatomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); @property (nonatomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size);
@property (nonatomic) BOOL hasFetchedData;
@property (nonatomic, nullable) UIGestureRecognizer *gestureRecognizer; @property (nonatomic, nullable) UIGestureRecognizer *gestureRecognizer;
@property (nonatomic, nullable) id idGestureRecognizer; @property (nonatomic, nullable) id idGestureRecognizer;
@@ -99,6 +98,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@property (nonatomic) BOOL displayRangeStateChangedToYES; @property (nonatomic) BOOL displayRangeStateChangedToYES;
@property (nonatomic) BOOL displayRangeStateChangedToNO; @property (nonatomic) BOOL displayRangeStateChangedToNO;
@property (nonatomic) BOOL hasPreloaded;
@property (nonatomic) BOOL preloadStateChangedToYES; @property (nonatomic) BOOL preloadStateChangedToYES;
@property (nonatomic) BOOL preloadStateChangedToNO; @property (nonatomic) BOOL preloadStateChangedToNO;
@end @end
@@ -113,18 +113,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\
return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero; return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero;
} }
- (void)fetchData
{
[super fetchData];
self.hasFetchedData = YES;
}
- (void)clearFetchedData
{
[super clearFetchedData];
self.hasFetchedData = NO;
}
- (void)didEnterDisplayState - (void)didEnterDisplayState
{ {
[super didEnterDisplayState]; [super didEnterDisplayState];
@@ -141,6 +129,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
{ {
[super didEnterPreloadState]; [super didEnterPreloadState];
self.preloadStateChangedToYES = YES; self.preloadStateChangedToYES = YES;
self.hasPreloaded = YES;
} }
- (void)didExitPreloadState - (void)didExitPreloadState
@@ -1738,76 +1727,76 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
} }
// Check that nodes who have no cell node (no range controller) // Check that nodes who have no cell node (no range controller)
// do get their `fetchData` called, and they do report // do get their `preload` called, and they do report
// the fetch data interface state. // the preload interface state.
- (void)testInterfaceStateForNonCellNode - (void)testInterfaceStateForNonCellNode
{ {
ASTestWindow *window = [ASTestWindow new]; ASTestWindow *window = [ASTestWindow new];
ASTestDisplayNode *node = [ASTestDisplayNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new];
XCTAssert(node.interfaceState == ASInterfaceStateNone); XCTAssert(node.interfaceState == ASInterfaceStateNone);
XCTAssert(!node.hasFetchedData); XCTAssert(!node.hasPreloaded);
[window addSubview:node.view]; [window addSubview:node.view];
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy);
[node.view removeFromSuperview]; [node.view removeFromSuperview];
// We don't want to call -clearFetchedData on nodes that aren't being managed by a range controller. // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller.
// Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop.
// Still, the interfaceState should be None to reflect the current state of the node. // Still, the interfaceState should be None to reflect the current state of the node.
// We just don't proactively clear contents or fetched data for this state transition. // We just don't proactively clear contents or fetched data for this state transition.
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
XCTAssert(node.interfaceState == ASInterfaceStateNone); XCTAssert(node.interfaceState == ASInterfaceStateNone);
} }
// Check that nodes who have no cell node (no range controller) // Check that nodes who have no cell node (no range controller)
// do get their `fetchData` called, and they do report // do get their `preload` called, and they do report
// the fetch data interface state. // the preload interface state.
- (void)testInterfaceStateForCellNode - (void)testInterfaceStateForCellNode
{ {
ASCellNode *cellNode = [ASCellNode new]; ASCellNode *cellNode = [ASCellNode new];
ASTestDisplayNode *node = [ASTestDisplayNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new];
XCTAssert(node.interfaceState == ASInterfaceStateNone); XCTAssert(node.interfaceState == ASInterfaceStateNone);
XCTAssert(!node.hasFetchedData); XCTAssert(!node.hasPreloaded);
// Simulate range handler updating cell node. // Simulate range handler updating cell node.
[cellNode addSubnode:node]; [cellNode addSubnode:node];
[cellNode enterInterfaceState:ASInterfaceStatePreload]; [cellNode enterInterfaceState:ASInterfaceStatePreload];
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
XCTAssert(node.interfaceState == ASInterfaceStatePreload); XCTAssert(node.interfaceState == ASInterfaceStatePreload);
// If the node goes into a view it should not adopt the `InHierarchy` state. // If the node goes into a view it should not adopt the `InHierarchy` state.
ASTestWindow *window = [ASTestWindow new]; ASTestWindow *window = [ASTestWindow new];
[window addSubview:cellNode.view]; [window addSubview:cellNode.view];
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy);
} }
- (void)testSetNeedsDataFetchImmediateState - (void)testSetNeedsPreloadImmediateState
{ {
ASCellNode *cellNode = [ASCellNode new]; ASCellNode *cellNode = [ASCellNode new];
ASTestDisplayNode *node = [ASTestDisplayNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new];
[cellNode addSubnode:node]; [cellNode addSubnode:node];
[cellNode enterInterfaceState:ASInterfaceStatePreload]; [cellNode enterInterfaceState:ASInterfaceStatePreload];
node.hasFetchedData = NO; node.hasPreloaded = NO;
[cellNode setNeedsDataFetch]; [cellNode setNeedsPreload];
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
} }
- (void)testFetchDataExitingAndEnteringRange - (void)testPreloadExitingAndEnteringRange
{ {
ASCellNode *cellNode = [ASCellNode new]; ASCellNode *cellNode = [ASCellNode new];
ASTestDisplayNode *node = [ASTestDisplayNode new]; ASTestDisplayNode *node = [ASTestDisplayNode new];
[cellNode addSubnode:node]; [cellNode addSubnode:node];
[cellNode setHierarchyState:ASHierarchyStateRangeManaged]; [cellNode setHierarchyState:ASHierarchyStateRangeManaged];
// Simulate enter range, fetch data, exit range // Simulate enter range, preload, exit range
[cellNode enterInterfaceState:ASInterfaceStatePreload]; [cellNode enterInterfaceState:ASInterfaceStatePreload];
[cellNode exitInterfaceState:ASInterfaceStatePreload]; [cellNode exitInterfaceState:ASInterfaceStatePreload];
node.hasFetchedData = NO; node.hasPreloaded = NO;
[cellNode enterInterfaceState:ASInterfaceStatePreload]; [cellNode enterInterfaceState:ASInterfaceStatePreload];
XCTAssert(node.hasFetchedData); XCTAssert(node.hasPreloaded);
} }
- (void)testInitWithViewClass - (void)testInitWithViewClass
@@ -2070,8 +2059,8 @@ static bool stringContainsPointer(NSString *description, id p) {
XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload);
XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload);
XCTAssertTrue(node.hasFetchedData); XCTAssertTrue(node.hasPreloaded);
XCTAssertTrue(subnode.hasFetchedData); XCTAssertTrue(subnode.hasPreloaded);
} }
// FIXME // FIXME

View File

@@ -133,7 +133,7 @@
XCTAssertNil(_videoNode.player); XCTAssertNil(_videoNode.player);
} }
- (void)testPlayerIsCreatedAsynchronouslyInFetchData - (void)testPlayerIsCreatedAsynchronouslyInPreload
{ {
AVAsset *asset = _firstAsset; AVAsset *asset = _firstAsset;
@@ -151,7 +151,7 @@
XCTAssertNotNil(_videoNode.player); XCTAssertNotNil(_videoNode.player);
} }
- (void)testPlayerIsCreatedAsynchronouslyInFetchDataWithURL - (void)testPlayerIsCreatedAsynchronouslyInPreloadWithURL
{ {
AVAsset *asset = [AVAsset assetWithURL:_url]; AVAsset *asset = [AVAsset assetWithURL:_url];
@@ -387,7 +387,7 @@
XCTAssertNotEqual(firstImage, _videoNode.image); XCTAssertNotEqual(firstImage, _videoNode.image);
} }
- (void)testClearingFetchedContentShouldClearAssetData - (void)testClearingPreloadedContentShouldClearAssetData
{ {
AVAsset *asset = _firstAsset; AVAsset *asset = _firstAsset;
@@ -398,7 +398,7 @@
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys]; [[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
_videoNode.asset = assetMock; _videoNode.asset = assetMock;
[_videoNode fetchData]; [_videoNode didEnterPreloadState];
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
[videoNodeMock verifyWithDelay:1.0f]; [videoNodeMock verifyWithDelay:1.0f];
@@ -407,7 +407,7 @@
XCTAssertNotNil(_videoNode.currentItem); XCTAssertNotNil(_videoNode.currentItem);
XCTAssertNotNil(_videoNode.image); XCTAssertNotNil(_videoNode.image);
[_videoNode clearFetchedData]; [_videoNode didExitPreloadState];
XCTAssertNil(_videoNode.player); XCTAssertNil(_videoNode.player);
XCTAssertNil(_videoNode.currentItem); XCTAssertNil(_videoNode.currentItem);
XCTAssertNil(_videoNode.image); XCTAssertNil(_videoNode.image);