Skip to content

Commit

Permalink
Updates from Tue Feb 24
Browse files Browse the repository at this point in the history
- Only update Redbox when offscreen | Nick Lockwood
- [react-packager] Cleanup option passing and validation | Amjad Masad
- Unified bridge implementation between OSS and private React forks | Nick Lockwood
- [react-packager][cleanup options 1/2] add npm installed joi validation library | Amjad Masad
  • Loading branch information
vjeux committed Feb 25, 2015
1 parent 1f8740a commit c892d2c
Show file tree
Hide file tree
Showing 25 changed files with 655 additions and 168 deletions.
20 changes: 17 additions & 3 deletions ReactKit/Base/RCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"

@class RCTBridge;
@class RCTEventDispatcher;
@class RCTRootView;

Expand All @@ -26,6 +27,12 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
return @{@"apiError": msg ?: @""};
}

/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge);

/**
* Async batched bridge used to communicate with the JavaScript application.
*/
Expand All @@ -34,11 +41,13 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
/**
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. You can optionally pass in
* a list of module instances to be used instead of the auto-instantiated versions.
* with the JavaScript code running in the executor. Modules will be automatically
* instantiated using the default contructor, but you can optionally pass in a
* module provider block to manually instantiate modules that require additional
* init parameters or configuration.
*/
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleInstances:(NSArray *)moduleInstances NS_DESIGNATED_INITIALIZER;
moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER;

/**
* This method is used to call functions in the JavaScript application context.
Expand All @@ -60,6 +69,11 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
*/
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;

/**
* A dictionary of all registered RCTBridgeModule instances, keyed by moduleName.
*/
@property (nonatomic, copy, readonly) NSDictionary *modules;

/**
* The shadow queue is used to execute callbacks from the JavaScript code. All
* native hooks (e.g. exported module methods) will be executed on the shadow
Expand Down
117 changes: 72 additions & 45 deletions ReactKit/Base/RCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#import <objc/runtime.h>

#import "RCTConvert.h"
#import "RCTInvalidating.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTSparseArray.h"
Expand Down Expand Up @@ -101,6 +100,40 @@ - (NSString *)description
}
}

/**
* This function scans all classes available at runtime and returns an array
* of all JSMethods registered.
*/
static NSArray *RCTJSMethods(void)
{
static NSArray *JSMethods;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *uniqueMethods = [NSMutableSet set];

unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {

Class cls = classes[i];

if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}

if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[uniqueMethods addObjectsFromArray:[cls JSMethods]];
}
}
free(classes);

JSMethods = [uniqueMethods allObjects];
});

return JSMethods;
}

/**
* This function scans all classes available at runtime and returns an array
* of all classes that implement the RTCBridgeModule protocol.
Expand Down Expand Up @@ -264,7 +297,7 @@ - (NSString *)description

NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) {
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
methodsByName[method.JSMethodName] = @{
@"methodID": @(methodID),
@"type": @"remote",
Expand Down Expand Up @@ -335,38 +368,16 @@ - (NSString *)description
static NSMutableDictionary *localModules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];

NSMutableArray *JSMethods = [[NSMutableArray alloc] init];

// Add globally used methods
[JSMethods addObjectsFromArray:@[
@"AppRegistry.runApplication",
@"RCTDeviceEventEmitter.emit",
@"RCTEventEmitter.receiveEvent",
@"RCTEventEmitter.receiveTouches",
]];

// NOTE: these methods are currently unused in the OSS project
// @"Dimensions.set",
// @"RCTNativeAppEventEmitter.emit",
// @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",

// Register individual methods from modules
for (Class cls in RCTBridgeModuleClassesByModuleID()) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[JSMethods addObjectsFromArray:[cls JSMethods]];
}
}


localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in JSMethods) {
for (NSString *moduleDotMethod in RCTJSMethods()) {

NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);

// Add module if it doesn't already exist
NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName];
Expand All @@ -377,7 +388,7 @@ - (NSString *)description
};
localModules[moduleName] = module;
}

// Add method if it doesn't already exist
NSString *methodName = parts[1];
NSMutableDictionary *methods = module[@"methods"];
Expand All @@ -387,27 +398,27 @@ - (NSString *)description
@"type": @"local"
};
}

// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
}
});

return localModules;
}

@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
NSMutableDictionary *_modulesByName;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
}

static id<RCTJavaScriptExecutor> _latestJSExecutor;

- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleInstances:(NSArray *)moduleInstances
moduleProvider:(RCTBridgeModuleProviderBlock)block
{
if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor;
Expand All @@ -417,31 +428,39 @@ - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScript

// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in moduleInstances) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
if (block) {
for (id<RCTBridgeModule> module in block(self)) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
}
}

// Instantiate modules
_modulesByID = [[RCTSparseArray alloc] init];
_modulesByName = [[NSMutableDictionary alloc] initWithDictionary:preregisteredModules];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSString *moduleName = RCTModuleNamesByID[moduleID];
// Check if module instance has already been registered for this name
if (_modulesByName[moduleName] != nil) {
if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
// Preregistered instances takes precedence, no questions asked
if (!preregisteredModules[moduleName]) {
// It's OK to have a name collision as long as the second instance is nil
RCTAssert(RCTCreateModuleInstance(moduleClass, self) == nil,
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
but name was already registered by class %@", moduleClass,
moduleName, [_modulesByName[moduleName] class]);
moduleName, [modulesByName[moduleName] class]);
}
} else {
// Module name hasn't been used before, so go ahead and instantiate
_modulesByID[moduleID] = _modulesByName[moduleName] = RCTCreateModuleInstance(moduleClass, self);
id<RCTBridgeModule> module = RCTCreateModuleInstance(moduleClass, self);
if (module) {
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
}
}];


// Store modules
_modulesByName = [modulesByName copy];

// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
Expand All @@ -460,6 +479,14 @@ - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScript
return self;
}

- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \
You may be trying to access a module too early in the startup procedure.");

return _modulesByName;
}

- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
Expand Down Expand Up @@ -516,7 +543,7 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args

[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args]];
arguments:@[moduleID, methodID, args ?: @[]]];
}

- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
Expand Down Expand Up @@ -699,8 +726,8 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
switch (argumentType[0]) {
case ':':
if ([param isKindOfClass:[NSString class]]) {
SEL selector = NSSelectorFromString(param);
[invocation setArgument:&selector atIndex:argIdx];
SEL sel = NSSelectorFromString(param);
[invocation setArgument:&sel atIndex:argIdx];
shouldSet = NO;
}
break;
Expand Down Expand Up @@ -813,7 +840,7 @@ + (BOOL)hasValidJSExecutor
+ (void)log:(NSArray *)objects level:(NSString *)level
{
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
RCTLogError(@"%@", RCTLogFormatString(@"ERROR: No valid JS executor to log %@.", objects));
RCTLogError(@"ERROR: No valid JS executor to log %@.", objects);
return;
}
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
Expand Down
19 changes: 9 additions & 10 deletions ReactKit/Base/RCTBridgeModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#import <Foundation/Foundation.h>

#import "RCTJSMethodRegistrar.h"

@class RCTBridge;

/**
Expand All @@ -11,9 +13,9 @@
typedef void (^RCTResponseSenderBlock)(NSArray *response);

/**
* Provides minimal interface needed to register a bridge module
* Provides the interface needed to register a bridge module.
*/
@protocol RCTBridgeModule <NSObject>
@protocol RCTBridgeModule <RCTJSMethodRegistrar>
@optional

/**
Expand Down Expand Up @@ -47,15 +49,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
+ (NSDictionary *)constantsToExport;

/**
* An array of JavaScript methods that the module will call via the
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
* method that has not been registered will result in an error. If a method
* has already been regsistered by another module, it is not necessary to
* register it again, but it is good pratice. Registering the same method
* more than once is silently ignored and will not result in an error.
* Some "constants" are not really constant, and need to be re-generated
* each time the bridge module is created. Support for this feature is
* deprecated and may be going away or changing, but for now you can use
* the -constantsToExport instance method to register these "pseudo-constants".
*/
+ (NSArray *)JSMethods;
- (NSDictionary *)constantsToExport;

/**
* Notifies the module that a batch of JS method invocations has just completed.
Expand Down
12 changes: 9 additions & 3 deletions ReactKit/Base/RCTEventDispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
- (instancetype)initWithBridge:(RCTBridge *)bridge;

/**
* Send a device or application event that does not relate to a specific
* view, e.g. rotation, location, keyboard show/hide, background/awake, etc.
* Send an application-specific event that does not relate to a specific
* view, e.g. a navigation or data update notification.
*/
- (void)sendDeviceEventWithName:(NSString *)name body:(NSDictionary *)body;
- (void)sendAppEventWithName:(NSString *)name body:(id)body;

/**
* Send a device or iOS event that does not relate to a specific view,
* e.g.rotation, location, keyboard show/hide, background/awake, etc.
*/
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body;

/**
* Send a user input event. The body dictionary must contain a "target"
Expand Down
Loading

0 comments on commit c892d2c

Please sign in to comment.