mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
[ASRangeController] Inspect delegate's ASInterfaceState to delay preloading beyond viewport until visible.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#import "ASCollectionNode.h"
|
||||
#import "ASCollectionInternal.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASRangeController.h"
|
||||
#include <vector>
|
||||
|
||||
@interface _ASCollectionPendingState : NSObject
|
||||
@@ -97,12 +98,12 @@
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASCollectionView *view = self.view;
|
||||
view.collectionNode = self;
|
||||
|
||||
if (_pendingState) {
|
||||
_ASCollectionPendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
|
||||
ASCollectionView *view = self.view;
|
||||
view.collectionNode = self;
|
||||
self.pendingState = nil;
|
||||
view.asyncDelegate = pendingState.delegate;
|
||||
view.asyncDataSource = pendingState.dataSource;
|
||||
}
|
||||
@@ -160,10 +161,13 @@
|
||||
return (ASCollectionView *)[super view];
|
||||
}
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
- (void)visibilityDidChange:(BOOL)isVisible
|
||||
{
|
||||
[super visibilityDidChange:isVisible];
|
||||
NSLog(@"%@ - visible: %d", self, isVisible);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
|
||||
@@ -782,6 +782,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
return self.collectionNode.interfaceState;
|
||||
}
|
||||
|
||||
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
return [_dataController nodesAtIndexPaths:indexPaths];
|
||||
@@ -944,6 +949,27 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#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.
|
||||
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
BOOL visible = (newWindow != nil);
|
||||
ASDisplayNode *node = self.collectionNode;
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
BOOL visible = (self.window != nil);
|
||||
ASDisplayNode *node = self.collectionNode;
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionView dead-end intercepts
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting.
|
||||
|
||||
@@ -66,8 +66,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
|
||||
ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay | ASInterfaceStateVisible,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
||||
* hierarchy off the main thread, and could do rendering off the main thread as well.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "ASFlowLayoutController.h"
|
||||
#import "ASTableViewInternal.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASRangeController.h"
|
||||
|
||||
@interface _ASTablePendingState : NSObject
|
||||
@property (weak, nonatomic) id <ASTableDelegate> delegate;
|
||||
@@ -63,11 +64,12 @@
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASTableView *view = self.view;
|
||||
view.tableNode = self;
|
||||
|
||||
if (_pendingState) {
|
||||
_ASTablePendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
|
||||
ASTableView *view = self.view;
|
||||
self.pendingState = nil;
|
||||
view.asyncDelegate = pendingState.delegate;
|
||||
view.asyncDataSource = pendingState.dataSource;
|
||||
}
|
||||
@@ -125,6 +127,14 @@
|
||||
return (ASTableView *)[super view];
|
||||
}
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
- (void)visibilityDidChange:(BOOL)isVisible
|
||||
{
|
||||
[super visibilityDidChange:isVisible];
|
||||
NSLog(@"%@ - visible: %d", self, isVisible);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
[super clearContents];
|
||||
|
||||
@@ -121,6 +121,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
// This also permits sharing logic with ASCollectionNode, as the superclass is not UIKit-controlled.
|
||||
@property (nonatomic, retain) ASTableNode *strongTableNode;
|
||||
|
||||
// Always set, whether ASCollectionView is created directly or via ASCollectionNode.
|
||||
@property (nonatomic, weak) ASTableNode *tableNode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTableView
|
||||
@@ -700,6 +703,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
return self.tableNode.interfaceState;
|
||||
}
|
||||
|
||||
#pragma mark - ASRangeControllerDelegate
|
||||
|
||||
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
@@ -943,4 +951,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#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.
|
||||
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
BOOL visible = (newWindow != nil);
|
||||
ASDisplayNode *node = self.tableNode;
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
BOOL visible = (self.window != nil);
|
||||
ASDisplayNode *node = self.tableNode;
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
@interface ASTableView (Internal)
|
||||
|
||||
@property (nonatomic, retain, readonly) ASDataController *dataController;
|
||||
@property (nonatomic, weak, readwrite) ASTableNode *tableNode;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASLayoutController.h>
|
||||
|
||||
#define RangeControllerLoggingEnabled 0
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASRangeControllerDataSource;
|
||||
@@ -102,6 +104,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
/**
|
||||
* @param rangeController Sender.
|
||||
*
|
||||
* @returns the ASInterfaceState of the node that this controller is powering. This allows nested range controllers
|
||||
* to collaborate with one another, as an outer controller may set bits in .interfaceState such as Visible.
|
||||
* If this controller is an orthogonally scrolling element, it waits until it is visible to preload outside the viewport.
|
||||
*/
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths;
|
||||
|
||||
- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@@ -17,6 +17,21 @@
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
|
||||
extern BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible);
|
||||
}
|
||||
|
||||
extern BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay);
|
||||
}
|
||||
|
||||
extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData);
|
||||
}
|
||||
|
||||
@interface ASRangeControllerBeta ()
|
||||
{
|
||||
BOOL _rangeIsValid;
|
||||
@@ -79,52 +94,91 @@
|
||||
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
|
||||
}
|
||||
|
||||
NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
|
||||
NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
|
||||
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
|
||||
|
||||
NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
|
||||
|
||||
//NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
|
||||
//NSLog(@"visible sets are equal: %d", [visibleIndexPaths isEqualToSet:visibleNodePathsSet]);
|
||||
|
||||
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
|
||||
NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy];
|
||||
[allIndexPaths unionSet:displayIndexPaths];
|
||||
[allIndexPaths unionSet:visibleIndexPaths];
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
NSMutableArray *modified = [NSMutableArray array];
|
||||
#endif
|
||||
|
||||
for (NSIndexPath *indexPath in allIndexPaths) {
|
||||
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
|
||||
// For consistency, make sure each node knows that it should measure itself if something changes.
|
||||
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
|
||||
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
|
||||
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
|
||||
NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
|
||||
NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
|
||||
|
||||
if ([fetchDataIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateFetchData;
|
||||
}
|
||||
if ([displayIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateDisplay;
|
||||
}
|
||||
if ([visibleIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateVisible;
|
||||
}
|
||||
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
|
||||
NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy];
|
||||
[allIndexPaths unionSet:displayIndexPaths];
|
||||
[allIndexPaths unionSet:visibleIndexPaths];
|
||||
|
||||
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
|
||||
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.
|
||||
if (node.interfaceState != interfaceState) {
|
||||
[modified addObject:indexPath];
|
||||
[node recursivelySetInterfaceState:interfaceState];
|
||||
for (NSIndexPath *indexPath in allIndexPaths) {
|
||||
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
|
||||
// For consistency, make sure each node knows that it should measure itself if something changes.
|
||||
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
|
||||
|
||||
if ([fetchDataIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateFetchData;
|
||||
}
|
||||
if ([displayIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateDisplay;
|
||||
}
|
||||
if ([visibleIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateVisible;
|
||||
}
|
||||
|
||||
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
|
||||
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.
|
||||
if (node.interfaceState != interfaceState) {
|
||||
#if RangeControllerLoggingEnabled
|
||||
[modified addObject:indexPath];
|
||||
#endif
|
||||
[node recursivelySetInterfaceState:interfaceState];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
// We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
|
||||
|
||||
for (NSIndexPath *indexPath in visibleIndexPaths) {
|
||||
// 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.
|
||||
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay;
|
||||
|
||||
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
|
||||
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.
|
||||
if (node.interfaceState != interfaceState) {
|
||||
#if RangeControllerLoggingEnabled
|
||||
[modified addObject:indexPath];
|
||||
#endif
|
||||
[node recursivelySetInterfaceState:interfaceState];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#if RangeControllerLoggingEnabled
|
||||
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
|
||||
BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet];
|
||||
NSLog(@"visible sets are equal: %d", setsAreEqual);
|
||||
if (!setsAreEqual) {
|
||||
NSLog(@"standard: %@", visibleIndexPaths);
|
||||
NSLog(@"custom: %@", visibleNodePathsSet);
|
||||
}
|
||||
|
||||
[modified sortUsingSelector:@selector(compare:)];
|
||||
|
||||
for (NSIndexPath *indexPath in modified) {
|
||||
NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, [visibleIndexPaths containsObject:indexPath], [displayIndexPaths containsObject:indexPath], [fetchDataIndexPaths containsObject:indexPath]);
|
||||
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
|
||||
ASInterfaceState interfaceState = node.interfaceState;
|
||||
BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState);
|
||||
BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState);
|
||||
BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState);
|
||||
NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData);
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
||||
_rangeIsValid = YES;
|
||||
_queuedRangeUpdate = NO;
|
||||
|
||||
@@ -66,6 +66,13 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
|
||||
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState;
|
||||
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState;
|
||||
|
||||
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
||||
@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy;
|
||||
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
||||
- (void)__enterHierarchy;
|
||||
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
||||
- (void)__exitHierarchy;
|
||||
|
||||
/**
|
||||
* @abstract Returns the Hierarchy State of the node.
|
||||
*
|
||||
|
||||
@@ -132,20 +132,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
- (void)__layout;
|
||||
- (void)__setSupernode:(ASDisplayNode *)supernode;
|
||||
|
||||
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
||||
@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy;
|
||||
|
||||
// Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this.
|
||||
- (BOOL)__visibilityNotificationsDisabled;
|
||||
- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled;
|
||||
- (void)__incrementVisibilityNotificationsDisabled;
|
||||
- (void)__decrementVisibilityNotificationsDisabled;
|
||||
|
||||
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
||||
- (void)__enterHierarchy;
|
||||
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
||||
- (void)__exitHierarchy;
|
||||
|
||||
// Helper method to summarize whether or not the node run through the display process
|
||||
- (BOOL)__implementsDisplay;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user