Add ASLayoutRangeModeLowMemory

This commit is contained in:
Michael Schneider 2016-03-09 14:40:40 -08:00
parent de4e4db671
commit bf822dee37
6 changed files with 93 additions and 14 deletions

View File

@ -167,7 +167,7 @@
return (ASCollectionView *)[super view]; return (ASCollectionView *)[super view];
} }
#if RangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibilityDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibilityDidChange:isVisible];

View File

@ -138,7 +138,7 @@
return (ASTableView *)[super view]; return (ASTableView *)[super view];
} }
#if RangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibilityDidChange:(BOOL)isVisible - (void)visibilityDidChange:(BOOL)isVisible
{ {
[super visibilityDidChange:isVisible]; [super visibilityDidChange:isVisible];

View File

@ -51,6 +51,15 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar
.trailingBufferScreenfuls = 2 .trailingBufferScreenfuls = 2
}; };
_tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypeDisplay] = {
.leadingBufferScreenfuls = 0,
.trailingBufferScreenfuls = 0
};
_tuningParameters[ASLayoutRangeModeLowMemory][ASLayoutRangeTypeFetchData] = {
.leadingBufferScreenfuls = 0,
.trailingBufferScreenfuls = 0
};
return self; return self;
} }

View File

@ -20,12 +20,20 @@ typedef NS_ENUM(NSUInteger, ASLayoutRangeMode) {
* Range controller can automatically switch to full mode when conditions change. * Range controller can automatically switch to full mode when conditions change.
*/ */
ASLayoutRangeModeMinimum = 0, ASLayoutRangeModeMinimum = 0,
/** /**
* Normal/Full mode that a range controller uses to provide the best experience for end users. * Normal/Full mode that a range controller uses to provide the best experience for end users.
* This mode is usually used for an active scroll view. * This mode is usually used for an active scroll view.
* A range controller under this requires more resources compare to minimum mode. * A range controller under this requires more resources compare to minimum mode.
*/ */
ASLayoutRangeModeFull, ASLayoutRangeModeFull,
/**
* Low Memory mode is used when a range controller should limit the amount of work it performs to 0.
* Thus, it discards most of the views/layers that are created and it is trying to save as much system
* resources as possible.
*/
ASLayoutRangeModeLowMemory,
ASLayoutRangeModeCount ASLayoutRangeModeCount
}; };

View File

@ -13,7 +13,8 @@
#import <AsyncDisplayKit/ASLayoutController.h> #import <AsyncDisplayKit/ASLayoutController.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h> #import <AsyncDisplayKit/ASLayoutRangeType.h>
#define RangeControllerLoggingEnabled 0 #define ASRangeControllerLoggingEnabled 0
#define ASRangeControllerAutomaticLowMemoryHandling 0
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

View File

@ -9,6 +9,7 @@
#import "ASRangeController.h" #import "ASRangeController.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASWeakSet.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASMultiDimensionalArrayUtils.h" #import "ASMultiDimensionalArrayUtils.h"
@ -32,6 +33,29 @@
@implementation ASRangeController @implementation ASRangeController
#pragma mark - NSObject
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self registerLowMemoryNotification];
});
}
#pragma mark - Class
+ (ASWeakSet *)rangeControllers
{
static ASWeakSet<ASRangeController *> *rangeController;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rangeController = [[ASWeakSet alloc] init];
});
return rangeController;
}
#pragma mark - Lifecycle
- (instancetype)init - (instancetype)init
{ {
if (!(self = [super init])) { if (!(self = [super init])) {
@ -42,6 +66,8 @@
_currentRangeMode = ASLayoutRangeModeInvalid; _currentRangeMode = ASLayoutRangeModeInvalid;
_didUpdateCurrentRange = NO; _didUpdateCurrentRange = NO;
[[self.class rangeControllers] addObject:self];
return self; return self;
} }
@ -52,6 +78,33 @@
} }
} }
#pragma mark - Low Memory Handling
+ (void)registerLowMemoryNotification
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lowMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
+ (void)lowMemoryWarning
{
#if ASRangeControllerAutomaticLowMemoryHandling
ASWeakSet *rangeControllers = [self rangeControllers];
for (ASRangeController *rangeController in rangeControllers) {
if (rangeController.dataSource == nil) {
continue;
}
ASInterfaceState interfaceState = [rangeController.dataSource interfaceStateForRangeController:rangeController];
if (ASInterfaceStateIncludesDisplay(interfaceState)) {
continue;
}
[rangeController updateCurrentRangeWithMode:ASLayoutRangeModeLowMemory];
}
#endif
}
#pragma mark - Core visible node range managment API #pragma mark - Core visible node range managment API
+ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState + (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState
@ -158,7 +211,9 @@
ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode
rangeType:ASLayoutRangeTypeDisplay]; rangeType:ASLayoutRangeTypeDisplay];
if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) { if (rangeMode == ASLayoutRangeModeLowMemory) {
displayIndexPaths = [NSSet set];
} else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero)) {
displayIndexPaths = visibleIndexPaths; displayIndexPaths = visibleIndexPaths;
} else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) {
displayIndexPaths = fetchDataIndexPaths; displayIndexPaths = fetchDataIndexPaths;
@ -193,8 +248,8 @@
// This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings
[self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
#if RangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
#endif #endif
for (NSIndexPath *indexPath in allIndexPaths) { for (NSIndexPath *indexPath in allIndexPaths) {
@ -217,13 +272,19 @@
// 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, fetch data 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:.
// Set Layout, Fetch Data, Display. 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.
if ([allCurrentIndexPaths containsObject:indexPath]) { if ([allCurrentIndexPaths containsObject:indexPath]) {
// 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
// Set Layout, Fetch Data
interfaceState |= ASInterfaceStateFetchData;
if (rangeMode != ASLayoutRangeModeLowMemory) {
// Add Display.
// We might be looking at an indexPath that was previously in-range, but now we need to clear it. // We might be looking at an indexPath that was previously in-range, but now we need to clear it.
// In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths.
interfaceState |= ASInterfaceStateDisplay; interfaceState |= ASInterfaceStateDisplay;
interfaceState |= ASInterfaceStateFetchData; }
} }
} }
@ -245,7 +306,7 @@
ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset.");
// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
if (node.interfaceState != interfaceState) { if (node.interfaceState != interfaceState) {
#if RangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
[modifiedIndexPaths addObject:indexPath]; [modifiedIndexPaths addObject:indexPath];
#endif #endif
[node recursivelySetInterfaceState:interfaceState]; [node recursivelySetInterfaceState:interfaceState];
@ -261,7 +322,7 @@
_rangeIsValid = YES; _rangeIsValid = YES;
_queuedRangeUpdate = NO; _queuedRangeUpdate = NO;
#if RangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet];
NSLog(@"visible sets are equal: %d", setsAreEqual); NSLog(@"visible sets are equal: %d", setsAreEqual);