ComponentKit/Core/Action/CKComponentActionInternal.h (115 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <ComponentKit/CKDefines.h>
#if CK_NOT_SWIFT
#import <UIKit/UIKit.h>
#import <vector>
#import <RenderCore/RCAssert.h>
#import <ComponentKit/CKComponentScope.h>
#import <ComponentKit/CKTreeNode.h>
#import <ComponentKit/CKRenderComponentProtocol.h>
#import <type_traits>
@class CKComponent;
@class CKTreeNode;
typedef NS_ENUM(NSInteger, CKActionSendBehavior) {
/** Starts searching at the sender's next responder. Usually this is what you want to prevent infinite loops. */
CKActionSendBehaviorStartAtSenderNextResponder,
/** If the sender itself responds to the action, invoke the action on the sender. */
CKActionSendBehaviorStartAtSender,
};
class _CKTypedComponentDebugInitialTarget;
#pragma mark - Action Base
/** A base-class for typed components that doesn't use templates to avoid template bloat. */
class CKActionBase {
struct ScopedResponderAndKey {
CKScopedResponder *responder;
CKScopedResponderKey key;
};
protected:
/**
We support several different types of action variants. You don't need to use this value anywhere, it's set for you
by whatever initializer you end up using.
*/
enum class CKActionVariant {
RawSelector,
TargetSelector,
Responder,
Block,
BlockWithIdentifier
};
CKActionBase() noexcept;
CKActionBase(const CKActionBase&);
CKActionBase(id target, SEL selector) noexcept;
CKActionBase(const CKComponentScope &scope, SEL selector) noexcept;
CKActionBase(SEL selector, CKTreeNode *node) noexcept;
/** Legacy constructor for raw selector actions. Traverse up the mount responder chain. */
CKActionBase(SEL selector) noexcept;
CKActionBase(dispatch_block_t block) noexcept;
CKActionBase(dispatch_block_t block, void *functionPointer, CKScopedResponder *responder, CKScopedResponderKey key) noexcept;
~CKActionBase();
id initialTarget(CKComponent *sender) const;
CKActionSendBehavior defaultBehavior() const;
bool operator==(const CKActionBase& rhs) const;
// Destroying this field calls objc_destroyWeak. Since this is the only field
// that runs code on destruction, making this field the first field of this
// object saves an offset calculation instruction in the destructor.
__weak id _target;
ScopedResponderAndKey _scopedResponderAndKey;
dispatch_block_t _block;
CKActionVariant _variant;
void *_selectorOrIdentifier;
static CKComponent *componentFromContext(const CK::BaseSpecContext &context) noexcept;
static CKTreeNode *nodeFromContext(const CK::BaseSpecContext &context) noexcept;
public:
explicit operator bool() const noexcept;
bool isEqual(const CKActionBase &rhs) const noexcept {
return *this == rhs;
}
SEL selector() const noexcept;
dispatch_block_t block() const noexcept;
std::string identifier() const noexcept;
friend _CKTypedComponentDebugInitialTarget;
};
#pragma mark - Typed Helpers
template <typename... Ts> struct CKActionTypelist { };
/** Base case, recursion should stop here. */
void CKActionTypeVectorBuild(std::vector<const char *> &typeVector, const CKActionTypelist<> &list) noexcept;
/**
Recursion through variadic argument type unpacking. This allows us to build a vector of encoded const char * before
any actual arguments have been provided. All of this is done at compile-time.
*/
template<typename T, typename... Ts>
void CKActionTypeVectorBuild(std::vector<const char *> &typeVector, const CKActionTypelist<T, Ts...> &list) noexcept
{
typeVector.push_back(@encode(T));
CKActionTypeVectorBuild(typeVector, CKActionTypelist<Ts...>{});
}
void CKConfigureInvocationWithArguments(NSInvocation *invocation, NSInteger index) noexcept;
#pragma mark - Debug Helpers
template<typename... T>
class CKAction;
/**
Get the list of control actions attached to the components view (if it has any), for debug purposes.
@return map of CKAction<> attached to the specifiec component.
*/
std::unordered_map<UIControlEvents, std::vector<CKAction<UIEvent *>>> _CKComponentDebugControlActionsForComponent(CKComponent *const component);
/**
Access the initialTarget of an action, for debug purposes.
*/
class _CKTypedComponentDebugInitialTarget {
private:
CKActionBase &_action;
public:
_CKTypedComponentDebugInitialTarget(CKActionBase &action) : _action(action) { }
id get(CKComponent *sender) const {
#if DEBUG
return _action.initialTarget(sender);
#else
return nil;
#endif
}
BOOL isBlockBaseAction() const {
return _action._variant == CKActionBase::CKActionVariant::Block;
}
};
#if DEBUG
void _CKTypedComponentDebugCheckComponentScope(const CKComponentScope &scope, SEL selector, const std::vector<const char *> &typeEncodings) noexcept;
void _CKTypedComponentDebugCheckComponentNode(CKTreeNode *node, SEL selector, const std::vector<const char *> &typeEncodings) noexcept;
void _CKTypedComponentDebugCheckTargetSelector(id target, SEL selector, const std::vector<const char *> &typeEncodings) noexcept;
void _CKTypedComponentDebugCheckComponent(Class<CKComponentProtocol> componentClass, SEL selector, const std::vector<const char *> &typeEncodings) noexcept;
#endif
NSString *_CKComponentResponderChainDebugResponderChain(id responder) noexcept;
#pragma mark - Sending
struct CKActionInfo {
IMP imp;
id responder;
};
CKActionInfo CKActionFind(SEL selector, id target) noexcept;
template<typename... T>
static void CKActionSendResponderChain(SEL selector, id target, CKComponent *sender, T... args) {
const CKActionInfo info = CKActionFind(selector, target);
if (!info.responder) {
return;
}
RCCAssert([info.responder methodSignatureForSelector:selector].numberOfArguments <= sizeof...(args) + 3,
@"Target invocation contains too many arguments => sender: %@ | SEL: %@ | target: %@",
sender, NSStringFromSelector(selector), [target class]);
// ARC assumes all IMPs return an id and will try to retain void,
// so have to case the IMP since it returns void.
void (*typedFunction)(id, SEL, id, T...) = (void (*)(id, SEL, id, T...))info.imp;
typedFunction(info.responder, selector, sender, args...);
}
#endif