react-native/React/Views/RCTComponentData.m

406 lines
14 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) 2015-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 "RCTComponentData.h"
#import <objc/message.h>
#import "RCTBridge.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
#import "RCTViewManager.h"
#import "UIView+React.h"
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@interface RCTComponentProp : NSObject
@property (nonatomic, copy, readonly) NSString *type;
@property (nonatomic, copy) RCTPropBlock propBlock;
@end
@implementation RCTComponentProp
- (instancetype)initWithType:(NSString *)type
{
if ((self = [super init])) {
_type = [type copy];
}
return self;
}
@end
@implementation RCTComponentData
{
id<RCTComponent> _defaultView;
RCTShadowView *_defaultShadowView;
NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks;
NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks;
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
RCTBridge *_bridge;
}
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
@synthesize manager = _manager;
- (instancetype)initWithManagerClass:(Class)managerClass
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
_bridge = bridge;
_managerClass = managerClass;
_viewPropBlocks = [NSMutableDictionary new];
_shadowPropBlocks = [NSMutableDictionary new];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
_name = RCTBridgeModuleNameForClass(_managerClass);
RCTAssert(_name.length, @"Invalid moduleName '%@'", _name);
if ([_name hasSuffix:@"Manager"]) {
_name = [_name substringToIndex:_name.length - @"Manager".length];
}
}
return self;
}
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
- (RCTViewManager *)manager
{
if (!_manager) {
_manager = [_bridge moduleForClass:_managerClass];
}
return _manager;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary<NSString *, id> *)props
{
RCTAssertMainThread();
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
UIView *view = (UIView *)(props ? [self.manager viewWithProps:props] : [_manager view]);
view.reactTag = tag;
view.multipleTouchEnabled = YES;
view.userInteractionEnabled = YES; // required for touch handling
view.layer.allowsGroupOpacity = YES; // required for touch handling
return view;
}
- (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
{
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
RCTShadowView *shadowView = [self.manager shadowView];
shadowView.reactTag = tag;
shadowView.viewName = _name;
return shadowView;
}
- (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
{
BOOL shadowView = [defaultView isKindOfClass:[RCTShadowView class]];
NSMutableDictionary<NSString *, RCTPropBlock> *propBlocks = shadowView ? _shadowPropBlocks : _viewPropBlocks;
RCTPropBlock propBlock = propBlocks[name];
if (!propBlock) {
__weak RCTComponentData *weakSelf = self;
// Get type
SEL type = NULL;
NSString *keyPath = nil;
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", shadowView ? @"Shadow" : @"", name]);
Class managerClass = [_manager class];
if ([managerClass respondsToSelector:selector]) {
NSArray<NSString *> *typeAndKeyPath =
((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(managerClass, selector);
type = NSSelectorFromString([typeAndKeyPath[0] stringByAppendingString:@":"]);
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
} else {
propBlock = ^(__unused id view, __unused id json) {};
propBlocks[name] = propBlock;
return propBlock;
}
// Check for custom setter
if ([keyPath isEqualToString:@"__custom__"]) {
// Get custom setter
SEL customSetter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]);
propBlock = ^(id<RCTComponent> view, id json) {
((void (*)(id, SEL, id, id, id))objc_msgSend)(
weakSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, defaultView
);
};
} else {
// Disect keypath
NSString *key = name;
NSArray<NSString *> *parts = [keyPath componentsSeparatedByString:@"."];
if (parts) {
key = parts.lastObject;
parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}];
}
// Get property getter
SEL getter = NSSelectorFromString(key);
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[key substringToIndex:1].uppercaseString,
[key substringFromIndex:1]]);
// Build setter block
void (^setterBlock)(id target, id source, id json) = nil;
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
// Special case for event handlers
__weak RCTViewManager *weakManager = _manager;
setterBlock = ^(id target, __unused id source, id json) {
__weak id<RCTComponent> weakTarget = target;
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body];
((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag;
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body];
} : nil);
};
} else {
// Ordinary property handlers
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
#define RCT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
setterBlock = ^(id target, id source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
RCT_CASE(_C_SEL, SEL)
RCT_CASE(_C_CHARPTR, const char *)
RCT_CASE(_C_CHR, char)
RCT_CASE(_C_UCHR, unsigned char)
RCT_CASE(_C_SHT, short)
RCT_CASE(_C_USHT, unsigned short)
RCT_CASE(_C_INT, int)
RCT_CASE(_C_UINT, unsigned int)
RCT_CASE(_C_LNG, long)
RCT_CASE(_C_ULNG, unsigned long)
RCT_CASE(_C_LNG_LNG, long long)
RCT_CASE(_C_ULNG_LNG, unsigned long long)
RCT_CASE(_C_FLT, float)
RCT_CASE(_C_DBL, double)
RCT_CASE(_C_BOOL, BOOL)
RCT_CASE(_C_PTR, void *)
RCT_CASE(_C_ID, id)
case _C_STRUCT_B:
default: {
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
setterBlock = ^(id target, id source, id json) { \
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
}
}
}
propBlock = ^(__unused id view, __unused id json) {
// Follow keypath
id target = view;
for (NSString *part in parts) {
target = [target valueForKey:part];
}
if (json == (id)kCFNull) {
// Copy default property
id source = defaultView;
for (NSString *part in parts) {
source = [source valueForKey:part];
}
setterBlock(target, source, nil);
} else {
// Set property with json
setterBlock(target, nil, json);
}
};
}
if (RCT_DEBUG) {
// Provide more useful log feedback if there's an error
RCTPropBlock unwrappedBlock = propBlock;
propBlock = ^(id<RCTComponent> view, id json) {
NSString *logPrefix = [NSString stringWithFormat:
@"Error setting property '%@' of %@ with tag #%@: ",
name, weakSelf.name, view.reactTag];
RCTPerformBlockWithLogPrefix(^{ unwrappedBlock(view, json); }, logPrefix);
};
}
propBlocks[name] = [propBlock copy];
}
return propBlock;
}
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>)view
{
if (!view) {
return;
}
if (!_defaultView) {
_defaultView = [self createViewWithTag:nil props:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultView](view, json);
}];
}
- (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
{
if (!shadowView) {
return;
}
if (!_defaultShadowView) {
_defaultShadowView = [self createShadowViewWithTag:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
}];
[shadowView updateLayout];
}
- (NSDictionary<NSString *, id> *)viewConfig
{
NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customDirectEventTypes))) {
NSArray<NSString *> *events = [self.manager customDirectEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customDirectEventTypes must return an array, but %@ returned %@",
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
_managerClass, [events class]);
}
for (NSString *event in events) {
[directEvents addObject:RCTNormalizeInputEventName(event)];
}
}
NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) {
NSArray<NSString *> *events = [self.manager customBubblingEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customBubblingEventTypes must return an array, but %@ returned %@",
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
_managerClass, [events class]);
}
for (NSString *event in events) {
[bubblingEvents addObject:RCTNormalizeInputEventName(event)];
}
}
unsigned int count = 0;
NSMutableDictionary *propTypes = [NSMutableDictionary new];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
NSString *methodName = NSStringFromSelector(selector);
if ([methodName hasPrefix:@"propConfig"]) {
NSRange nameRange = [methodName rangeOfString:@"_"];
if (nameRange.length) {
NSString *name = [methodName substringFromIndex:nameRange.location + 1];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0];
if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) {
RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
"to '%@'", name, _name, propTypes[name], type);
}
if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
[bubblingEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
[directEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else {
propTypes[name] = type;
}
}
}
}
free(methods);
if (RCT_DEBUG) {
for (NSString *event in directEvents) {
if ([bubblingEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
for (NSString *event in bubblingEvents) {
if ([directEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
}
return @{
@"propTypes" : propTypes,
@"directEvents" : directEvents,
@"bubblingEvents" : bubblingEvents,
};
}
@end