-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds support for having multiple interface state delegates. #979
Changes from 1 commit
e8b8fd6
8c5981e
205f6d4
0dcfaec
2a1c063
700d927
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// | ||
// ASDisplayNode+InterfaceState.h | ||
// Texture | ||
// | ||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
/** | ||
* Interface state is available on ASDisplayNode and ASViewController, and | ||
* allows checking whether a node is in an interface situation where it is prudent to trigger certain | ||
* actions: measurement, data loading, display, and visibility (the latter for animations or other onscreen-only effects). | ||
* | ||
* The defualt state, ASInterfaceStateNone, means that the element is not predicted to be onscreen soon and | ||
* preloading should not be performed. Swift: use [] for the default behavior. | ||
*/ | ||
typedef NS_OPTIONS(NSUInteger, ASInterfaceState) | ||
{ | ||
/** The element is not predicted to be onscreen soon and preloading should not be performed */ | ||
ASInterfaceStateNone = 0, | ||
/** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */ | ||
ASInterfaceStateMeasureLayout = 1 << 0, | ||
/** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ | ||
ASInterfaceStatePreload = 1 << 1, | ||
/** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ | ||
ASInterfaceStateDisplay = 1 << 2, | ||
/** The element is physically onscreen by at least 1 pixel. | ||
In practice, all other bit fields should also be set when this flag is set. */ | ||
ASInterfaceStateVisible = 1 << 3, | ||
|
||
/** | ||
* The node is not contained in a cell but it is in a window. | ||
* | ||
* Currently we only set `interfaceState` to other values for | ||
* nodes contained in table views or collection views. | ||
*/ | ||
ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStatePreload | ASInterfaceStateDisplay | ASInterfaceStateVisible, | ||
}; | ||
|
||
@protocol ASInterfaceStateDelegate <NSObject> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only change here is that these are now all optional. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also added |
||
@optional | ||
|
||
/** | ||
* @abstract Called whenever any bit in the ASInterfaceState bitfield is changed. | ||
* @discussion Subclasses may use this to monitor when they become visible, should free cached data, and much more. | ||
* @see ASInterfaceState | ||
*/ | ||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState; | ||
|
||
/** | ||
* @abstract Called whenever the node becomes visible. | ||
* @discussion Subclasses may use this to monitor when they become visible. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didEnterVisibleState; | ||
|
||
/** | ||
* @abstract Called whenever the node is no longer visible. | ||
* @discussion Subclasses may use this to monitor when they are no longer visible. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didExitVisibleState; | ||
|
||
/** | ||
* @abstract Called whenever the the node has entered the display state. | ||
* @discussion Subclasses may use this to monitor when a node should be rendering its content. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didEnterDisplayState; | ||
|
||
/** | ||
* @abstract Called whenever the the node has exited the display state. | ||
* @discussion Subclasses may use this to monitor when a node should no longer be rendering its content. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didExitDisplayState; | ||
|
||
/** | ||
* @abstract Called whenever the the node has entered the preload state. | ||
* @discussion Subclasses may use this to monitor data for a node should be preloaded, either from a local or remote source. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didEnterPreloadState; | ||
|
||
/** | ||
* @abstract Called whenever the the node has exited the preload state. | ||
* @discussion Subclasses may use this to monitor whether preloading data for a node should be canceled. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)didExitPreloadState; | ||
|
||
/** | ||
* @abstract Called when the node has completed applying the layout. | ||
* @discussion Can be used for operations that are performed after layout has completed. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)nodeDidLayout; | ||
|
||
/** | ||
* @abstract Called when the node loads. | ||
* @discussion Can be used for operations that are performed after the node's view is available. | ||
* @note This method is guaranteed to be called on main. | ||
*/ | ||
- (void)nodeDidLoad; | ||
|
||
@end | ||
|
||
@interface ASDisplayNodeInterfaceDelegateManager : NSObject <ASInterfaceStateDelegate> | ||
|
||
- (void)addDelegate:(id <ASInterfaceStateDelegate>)delegate; | ||
- (void)removeDelegate:(id <ASInterfaceStateDelegate>)delegate; | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// | ||
// ASDisplayNode+InterfaceState.m | ||
// Texture | ||
// | ||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
|
||
#import <AsyncDisplayKit/ASDisplayNode+InterfaceState.h> | ||
|
||
@interface ASDisplayNodeInterfaceDelegateManager () | ||
{ | ||
NSHashTable *_interfaceDidChangeDelegates; | ||
NSHashTable *_interfaceDidEnterVisibleDelegates; | ||
NSHashTable *_interfaceDidExitVisibleDelegates; | ||
NSHashTable *_interfaceDidEnterDisplayDelegates; | ||
NSHashTable *_interfaceDidExitDisplayDelegates; | ||
NSHashTable *_interfaceDidEnterPreloadDelegates; | ||
NSHashTable *_interfaceDidExitPreloadDelegates; | ||
NSHashTable *_interfaceNodeDidLayoutDelegates; | ||
NSHashTable *_interfaceNodeDidLoadDelegates; | ||
} | ||
@end | ||
|
||
@implementation ASDisplayNodeInterfaceDelegateManager | ||
|
||
- (instancetype)init | ||
{ | ||
if (self = [super init]) { | ||
_interfaceDidChangeDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidEnterVisibleDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidExitVisibleDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidEnterDisplayDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidExitDisplayDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidEnterPreloadDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceDidExitPreloadDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceNodeDidLayoutDelegates = [NSHashTable weakObjectsHashTable]; | ||
_interfaceNodeDidLoadDelegates = [NSHashTable weakObjectsHashTable]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That'll let you remove this intermediary object and it won't be slower by any amount that matters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should have to create all these up front...since we would only have to use the respondsToSelector: once per class (not even once per delegate), we should get a significant win by relying on just one object tracking the delegates, and we could use a simple bitfield / NS_OPTIONS to cache the ASInterfaceStateDelegateMethodsImplemented (similar to the flags in ASCollection that cache what the delegate / datasource implement, but just an NS_OPTIONS). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In recent versions of Objective-C, Writing our own bitfield-by-class would amount to us reimplementing that portion of the impcache, without the benefit of direct access to the class method table. And even accessing our table – say it were in a singleton registry object – would require retaining-releasing it unless we coded around that, which would easily subsume the entire rest of the lookup. The added boilerplate is also nontrivial. So there is a win available here, but I believe it's small. Let's avoid these calls only if we can do it in a simple and tremendously fast & scalable way. If we can't, then we shouldn't worry about it – I believe we can absolutely achieve far superior optimizations by using resources for other parts of the framework (such as transfer-collections or more judicious applications of Implementations: |
||
} | ||
} | ||
|
||
- (void)addDelegate:(id<ASInterfaceStateDelegate>)delegate | ||
{ | ||
if ([delegate respondsToSelector:@selector(interfaceStateDidChange:fromState:)]) { | ||
[_interfaceDidChangeDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didEnterVisibleState)]) { | ||
[_interfaceDidEnterVisibleDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didExitVisibleState)]) { | ||
[_interfaceDidExitVisibleDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didEnterDisplayState)]) { | ||
[_interfaceDidEnterDisplayDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didExitDisplayState)]) { | ||
[_interfaceDidExitDisplayDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didEnterPreloadState)]) { | ||
[_interfaceDidEnterPreloadDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(didExitPreloadState)]) { | ||
[_interfaceDidExitPreloadDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(nodeDidLayout)]) { | ||
[_interfaceNodeDidLayoutDelegates addObject:delegate]; | ||
} | ||
if ([delegate respondsToSelector:@selector(nodeDidLoad)]) { | ||
[_interfaceNodeDidLoadDelegates addObject:delegate]; | ||
} | ||
} | ||
|
||
- (void)removeDelegate:(id<ASInterfaceStateDelegate>)delegate | ||
{ | ||
[_interfaceDidChangeDelegates removeObject:delegate]; | ||
[_interfaceDidEnterVisibleDelegates removeObject:delegate]; | ||
[_interfaceDidExitVisibleDelegates removeObject:delegate]; | ||
[_interfaceDidEnterDisplayDelegates removeObject:delegate]; | ||
[_interfaceDidExitDisplayDelegates removeObject:delegate]; | ||
[_interfaceDidEnterPreloadDelegates removeObject:delegate]; | ||
[_interfaceDidExitPreloadDelegates removeObject:delegate]; | ||
[_interfaceNodeDidLayoutDelegates removeObject:delegate]; | ||
[_interfaceNodeDidLoadDelegates removeObject:delegate]; | ||
} | ||
|
||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidChangeDelegates) { | ||
[delegate interfaceStateDidChange:newState fromState:oldState]; | ||
} | ||
} | ||
|
||
- (void)didEnterVisibleState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidEnterVisibleDelegates) { | ||
[delegate didEnterVisibleState]; | ||
} | ||
} | ||
|
||
- (void)didExitVisibleState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidExitVisibleDelegates) { | ||
[delegate didExitVisibleState]; | ||
} | ||
} | ||
|
||
- (void)didEnterDisplayState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidEnterDisplayDelegates) { | ||
[delegate didEnterDisplayState]; | ||
} | ||
} | ||
|
||
- (void)didExitDisplayState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidExitDisplayDelegates) { | ||
[delegate didExitDisplayState]; | ||
} | ||
} | ||
|
||
- (void)didEnterPreloadState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidEnterPreloadDelegates) { | ||
[delegate didEnterPreloadState]; | ||
} | ||
} | ||
|
||
- (void)didExitPreloadState | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceDidExitPreloadDelegates) { | ||
[delegate didExitPreloadState]; | ||
} | ||
} | ||
|
||
- (void)nodeDidLayout | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceNodeDidLayoutDelegates) { | ||
[delegate nodeDidLayout]; | ||
} | ||
} | ||
|
||
- (void)nodeDidLoad | ||
{ | ||
for (id <ASInterfaceStateDelegate>delegate in _interfaceNodeDidLoadDelegates) { | ||
[delegate nodeDidLoad]; | ||
} | ||
} | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just moved, no changes.