Skip to content

Commit

Permalink
Use flag for responder chain methods between view and node dispatching (
Browse files Browse the repository at this point in the history
facebookarchive#1522)

* Use flag for responder chain methods between view and node dispatching

* Use bitfields
  • Loading branch information
maicki authored May 30, 2019
1 parent a2abd00 commit 62e5a8e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 249 deletions.
93 changes: 1 addition & 92 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -170,24 +170,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesEnded;
}

// Responder chain
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canBecomeFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideCanBecomeFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(becomeFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideBecomeFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canResignFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideCanResignFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(resignFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideResignFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(isFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideIsFirstResponder;
}


// Layout related methods
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) {
overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits;
Expand Down Expand Up @@ -907,26 +890,6 @@ - (void)__setNodeController:(ASNodeController *)controller
}
}

#pragma mark - UIResponder

#define HANDLE_NODE_RESPONDER_METHOD(__sel) \
/* All responder methods should be called on the main thread */ \
ASDisplayNodeAssertMainThread(); \
if (checkFlag(Synchronous)) { \
/* If the view is not a _ASDisplayView subclass (Synchronous) just call through to the view as we
expect it's a non _ASDisplayView subclass that will respond */ \
return [_view __sel]; \
} else { \
if (ASSubclassOverridesSelector([_ASDisplayView class], _viewClass, @selector(__sel))) { \
/* If the subclass overwrites canBecomeFirstResponder just call through
to it as we expect it will handle it */ \
return [_view __sel]; \
} else { \
/* Call through to _ASDisplayView's superclass to get it handled */ \
return [(_ASDisplayView *)_view __##__sel]; \
} \
} \

- (void)checkResponderCompatibility
{
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
Expand All @@ -944,60 +907,6 @@ - (void)checkResponderCompatibility
#endif
}

- (BOOL)__canBecomeFirstResponder
{
if (_view == nil) {
// By default we return NO if not view is created yet
return NO;
}

HANDLE_NODE_RESPONDER_METHOD(canBecomeFirstResponder);
}

- (BOOL)__becomeFirstResponder
{
// Note: This implicitly loads the view if it hasn't been loaded yet.
[self view];

if (![self canBecomeFirstResponder]) {
return NO;
}

HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder);
}

- (BOOL)__canResignFirstResponder
{
if (_view == nil) {
// By default we return YES if no view is created yet
return YES;
}

HANDLE_NODE_RESPONDER_METHOD(canResignFirstResponder);
}

- (BOOL)__resignFirstResponder
{
// Note: This implicitly loads the view if it hasn't been loaded yet.
[self view];

if (![self canResignFirstResponder]) {
return NO;
}

HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder);
}

- (BOOL)__isFirstResponder
{
if (_view == nil) {
// If no view is created yet we can just return NO as it's unlikely it's the first responder
return NO;
}

HANDLE_NODE_RESPONDER_METHOD(isFirstResponder);
}

#pragma mark <ASDebugNameProvider>

- (NSString *)debugName
Expand Down
8 changes: 0 additions & 8 deletions Source/Details/_ASDisplayView.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)__forwardTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

// These methods expose a way for ASDisplayNode responder methods to let the view call super responder methods
// They are called from ASDisplayNode to pass through UIResponder methods to the view
- (BOOL)__canBecomeFirstResponder;
- (BOOL)__becomeFirstResponder;
- (BOOL)__canResignFirstResponder;
- (BOOL)__resignFirstResponder;
- (BOOL)__isFirstResponder;

@end

NS_ASSUME_NONNULL_END
207 changes: 80 additions & 127 deletions Source/Details/_ASDisplayView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,48 +21,6 @@
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/ASViewController.h>

#pragma mark - _ASDisplayViewMethodOverrides

typedef NS_OPTIONS(NSUInteger, _ASDisplayViewMethodOverrides)
{
_ASDisplayViewMethodOverrideNone = 0,
_ASDisplayViewMethodOverrideCanBecomeFirstResponder = 1 << 0,
_ASDisplayViewMethodOverrideBecomeFirstResponder = 1 << 1,
_ASDisplayViewMethodOverrideCanResignFirstResponder = 1 << 2,
_ASDisplayViewMethodOverrideResignFirstResponder = 1 << 3,
_ASDisplayViewMethodOverrideIsFirstResponder = 1 << 4,
};

/**
* Returns _ASDisplayViewMethodOverrides for the given class
*
* @param c the class, required.
*
* @return _ASDisplayViewMethodOverrides.
*/
static _ASDisplayViewMethodOverrides GetASDisplayViewMethodOverrides(Class c)
{
ASDisplayNodeCAssertNotNil(c, @"class is required");

_ASDisplayViewMethodOverrides overrides = _ASDisplayViewMethodOverrideNone;
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canBecomeFirstResponder))) {
overrides |= _ASDisplayViewMethodOverrideCanBecomeFirstResponder;
}
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(becomeFirstResponder))) {
overrides |= _ASDisplayViewMethodOverrideBecomeFirstResponder;
}
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canResignFirstResponder))) {
overrides |= _ASDisplayViewMethodOverrideCanResignFirstResponder;
}
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(resignFirstResponder))) {
overrides |= _ASDisplayViewMethodOverrideResignFirstResponder;
}
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(isFirstResponder))) {
overrides |= _ASDisplayViewMethodOverrideIsFirstResponder;
}
return overrides;
}

#pragma mark - _ASDisplayView

@interface _ASDisplayView ()
Expand All @@ -74,55 +32,28 @@ @interface _ASDisplayView ()

@implementation _ASDisplayView
{
BOOL _inHitTest;
BOOL _inPointInside;
struct _ASDisplayViewInternalFlags {
unsigned inHitTest:1;
unsigned inPointInside:1;

unsigned inCanBecomeFirstResponder:1;
unsigned inBecomeFirstResponder:1;
unsigned inCanResignFirstResponder:1;
unsigned inResignFirstResponder:1;
unsigned inIsFirstResponder:1;
} _internalFlags;

NSArray *_accessibilityElements;
CGRect _lastAccessibilityElementsFrame;

_ASDisplayViewMethodOverrides _methodOverrides;
}

#pragma mark - Class

+ (void)initialize
{
__unused Class initializeSelf = self;
IMP staticInitialize = imp_implementationWithBlock(^(_ASDisplayView *view) {
ASDisplayNodeAssert(view.class == initializeSelf, @"View class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", view.class, initializeSelf);
view->_methodOverrides = GetASDisplayViewMethodOverrides(view.class);
});

class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@");
}

+ (Class)layerClass
{
return [_ASDisplayLayer class];
}

#pragma mark - NSObject Overrides

- (instancetype)init
{
if (!(self = [super init]))
return nil;

[self _initializeInstance];

return self;
}

- (void)_initializeInstance
{
[self _staticInitialize];
}

- (void)_staticInitialize
{
ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden");
}

// e.g. <MYPhotoNodeView: 0xFFFFFF; node = <MYPhotoNode: 0xFFFFFE>; frame = ...>
- (NSString *)description
{
Expand Down Expand Up @@ -409,10 +340,10 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// hitTest:, it will call it on the view, which is _ASDisplayView. After calling into the node, any additional calls
// should use the UIView implementation of hitTest:
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_inHitTest) {
_inHitTest = YES;
if (!_internalFlags.inHitTest) {
_internalFlags.inHitTest = YES;
UIView *hitView = [node hitTest:point withEvent:event];
_inHitTest = NO;
_internalFlags.inHitTest = NO;
return hitView;
} else {
return [super hitTest:point withEvent:event];
Expand All @@ -423,10 +354,10 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// See comments in -hitTest:withEvent: for the strategy here.
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_inPointInside) {
_inPointInside = YES;
if (!_internalFlags.inPointInside) {
_internalFlags.inPointInside = YES;
BOOL result = [node pointInside:point withEvent:event];
_inPointInside = NO;
_internalFlags.inPointInside = NO;
return result;
} else {
return [super pointInside:point withEvent:event];
Expand All @@ -449,48 +380,70 @@ - (void)tintColorDidChange

#pragma mark UIResponder Handling

#define IMPLEMENT_RESPONDER_METHOD(__sel, __nodeMethodOverride, __viewMethodOverride) \
- (BOOL)__sel\
{\
ASDisplayNode *node = _asyncdisplaykit_node; /* Create strong reference to weak ivar. */ \
/* Check if we can call through to ASDisplayNode subclass directly */ \
if (node.methodOverrides & __nodeMethodOverride) { \
return [node __sel]; \
} else { \
/* Prevent an infinite loop in here if [super __sel] was called on a \
/ _ASDisplayView subclass */ \
if (self->_methodOverrides & __viewMethodOverride) { \
/* Call through to views superclass as we expect super was called from the
_ASDisplayView subclass and a node subclass does not overwrite __sel */ \
return [self __##__sel]; \
} else { \
/* Call through to internal node __sel method that will consider the view in responding */ \
return [node __##__sel]; \
} \
} \
}\
/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \
are not overridden by a ASDisplayNode subclass */ \
- (BOOL)__##__sel \
{ \
return [super __sel]; \
} \

IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder,
ASDisplayNodeMethodOverrideCanBecomeFirstResponder,
_ASDisplayViewMethodOverrideCanBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder,
ASDisplayNodeMethodOverrideBecomeFirstResponder,
_ASDisplayViewMethodOverrideBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder,
ASDisplayNodeMethodOverrideCanResignFirstResponder,
_ASDisplayViewMethodOverrideCanResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(resignFirstResponder,
ASDisplayNodeMethodOverrideResignFirstResponder,
_ASDisplayViewMethodOverrideResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(isFirstResponder,
ASDisplayNodeMethodOverrideIsFirstResponder,
_ASDisplayViewMethodOverrideIsFirstResponder);
- (BOOL)canBecomeFirstResponder
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_internalFlags.inCanBecomeFirstResponder) {
_internalFlags.inCanBecomeFirstResponder = YES;
BOOL result = [node canBecomeFirstResponder];
_internalFlags.inCanBecomeFirstResponder = NO;
return result;
} else {
return [super canBecomeFirstResponder];
}
}

- (BOOL)becomeFirstResponder
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_internalFlags.inBecomeFirstResponder) {
_internalFlags.inBecomeFirstResponder = YES;
BOOL result = [node becomeFirstResponder];
_internalFlags.inBecomeFirstResponder = NO;
return result;
} else {
return [super becomeFirstResponder];
}
}

- (BOOL)canResignFirstResponder
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_internalFlags.inCanResignFirstResponder) {
_internalFlags.inCanResignFirstResponder = YES;
BOOL result = [node canResignFirstResponder];
_internalFlags.inCanResignFirstResponder = NO;
return result;
} else {
return [super canResignFirstResponder];
}
}

- (BOOL)resignFirstResponder
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_internalFlags.inResignFirstResponder) {
_internalFlags.inResignFirstResponder = YES;
BOOL result = [node resignFirstResponder];
_internalFlags.inResignFirstResponder = NO;
return result;
} else {
return [super resignFirstResponder];
}
}

- (BOOL)isFirstResponder
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
if (!_internalFlags.inIsFirstResponder) {
_internalFlags.inIsFirstResponder = YES;
BOOL result = [node isFirstResponder];
_internalFlags.inIsFirstResponder = NO;
return result;
} else {
return [super isFirstResponder];
}
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
Expand Down
Loading

0 comments on commit 62e5a8e

Please sign in to comment.