2015-01-30 01:10:49 +00:00
// Copyright 2004 - present Facebook . All Rights Reserved .
# import "RCTBridge.h"
# import < objc / message . h >
# import "RCTModuleMethod.h"
# import "RCTInvalidating.h"
2015-02-04 00:02:36 +00:00
# import "RCTEventDispatcher.h"
2015-01-30 01:10:49 +00:00
# import "RCTLog.h"
# import "RCTUtils.h"
2015-02-04 00:12:07 +00:00
/ * *
* Functions are the one thing that aren ' t automatically converted to OBJC
* blocks , according to this revert : http : // trac . webkit . org / changeset / 144489
* They must be expressed as ` JSValue` s .
*
* But storing callbacks causes reference cycles !
* http : // stackoverflow . com / questions / 19202248 / how - can - i - use - jsmanagedvalue - to - avoid - a - reference - cycle - without - the - jsvalue - gett
* We ' ll live with the leak for now , but need to clean this up asap :
* Passing a reference to the ` context` to the bridge would make it easy to
* execute JS . We can add ` JSManagedValue` s to protect against this . The same
* needs to be done in ` RCTTiming` and friends .
* /
/ * *
* Must be kept in sync with ` MessageQueue . js` .
* /
typedef NS_ENUM ( NSUInteger , RCTBridgeFields ) {
RCTBridgeFieldRequestModuleIDs = 0 ,
RCTBridgeFieldMethodIDs ,
RCTBridgeFieldParamss ,
RCTBridgeFieldResponseCBIDs ,
RCTBridgeFieldResponseReturnValues ,
RCTBridgeFieldFlushDateMillis
} ;
static NSDictionary * RCTNativeModuleClasses ( void )
2015-01-30 01:10:49 +00:00
{
static NSMutableDictionary * modules ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
modules = [ NSMutableDictionary dictionary ] ;
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 ( ! [ cls conformsToProtocol : @ protocol ( RCTNativeModule ) ] ) {
// Not an RCTNativeModule
continue ;
}
// Get module name
2015-02-04 00:15:20 +00:00
NSString * moduleName = [ cls respondsToSelector : @ selector ( moduleName ) ] ? [ cls moduleName ] : NSStringFromClass ( cls ) ;
2015-01-30 01:10:49 +00:00
// Check module name is unique
id existingClass = modules [ moduleName ] ;
RCTCAssert ( existingClass = = Nil , @ "Attempted to register RCTNativeModule class %@ for the name '%@', but name was already registered by class %@" , cls , moduleName , existingClass ) ;
modules [ moduleName ] = cls ;
}
free ( classes ) ;
} ) ;
return modules ;
}
@ implementation RCTBridge
{
NSMutableDictionary * _moduleInstances ;
2015-02-04 00:15:20 +00:00
NSMutableDictionary * _moduleIDLookup ;
NSMutableDictionary * _methodIDLookup ;
2015-01-30 01:10:49 +00:00
id < RCTJavaScriptExecutor > _javaScriptExecutor ;
}
static id < RCTJavaScriptExecutor > _latestJSExecutor ;
- ( instancetype ) initWithJavaScriptExecutor : ( id < RCTJavaScriptExecutor > ) javaScriptExecutor
{
if ( ( self = [ super init ] ) ) {
_javaScriptExecutor = javaScriptExecutor ;
_latestJSExecutor = _javaScriptExecutor ;
2015-02-04 00:02:36 +00:00
_eventDispatcher = [ [ RCTEventDispatcher alloc ] initWithBridge : self ] ;
2015-02-04 00:12:07 +00:00
_shadowQueue = dispatch_queue _create ( "com.facebook.ReactKit.ShadowQueue" , DISPATCH_QUEUE _SERIAL ) ;
2015-02-04 00:15:20 +00:00
// Instantiate modules
2015-02-04 00:12:07 +00:00
_moduleInstances = [ [ NSMutableDictionary alloc ] init ] ;
2015-01-30 01:10:49 +00:00
[ RCTNativeModuleClasses ( ) enumerateKeysAndObjectsUsingBlock : ^ ( NSString * moduleName , Class moduleClass , BOOL * stop ) {
if ( _moduleInstances [ moduleName ] = = nil ) {
2015-02-04 00:12:07 +00:00
if ( [ moduleClass instancesRespondToSelector : @ selector ( initWithBridge : ) ] ) {
_moduleInstances [ moduleName ] = [ [ moduleClass alloc ] initWithBridge : self ] ;
} else {
_moduleInstances [ moduleName ] = [ [ moduleClass alloc ] init ] ;
}
2015-01-30 01:10:49 +00:00
}
} ] ;
2015-02-04 00:15:20 +00:00
_moduleIDLookup = [ [ NSMutableDictionary alloc ] init ] ;
_methodIDLookup = [ [ NSMutableDictionary alloc ] init ] ;
2015-01-30 01:10:49 +00:00
[ self doneRegisteringModules ] ;
}
return self ;
}
- ( void ) dealloc
{
RCTAssert ( ! self . valid , @ "must call -invalidate before -dealloc; TODO: why not call it here then?" ) ;
}
# pragma mark - RCTInvalidating
- ( BOOL ) isValid
{
return _javaScriptExecutor ! = nil ;
}
- ( void ) invalidate
{
if ( _latestJSExecutor = = _javaScriptExecutor ) {
_latestJSExecutor = nil ;
}
_javaScriptExecutor = nil ;
dispatch_sync ( _shadowQueue , ^ {
2015-02-04 00:12:07 +00:00
// Make sure all dispatchers have been executed before continuing
// TODO : is this still needed ?
2015-01-30 01:10:49 +00:00
} ) ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
for ( id target in _moduleInstances . objectEnumerator ) {
if ( [ target respondsToSelector : @ selector ( invalidate ) ] ) {
[ ( id < RCTInvalidating > ) target invalidate ] ;
}
}
[ _moduleInstances removeAllObjects ] ;
}
/ * *
* - TODO ( #5906496 ) : When we build a ` MessageQueue . m` , handling all the requests could
* cause both a queue of "responses" . We would flush them here . However , we
* currently just expect each objc block to handle its own response sending
* using a ` RCTResponseSenderBlock` .
* /
# pragma mark - RCTBridge methods
/ * *
* Like JS : : call , for objective - c .
* /
2015-02-04 00:15:20 +00:00
- ( void ) enqueueJSCall : ( NSString * ) moduleDotMethod args : ( NSArray * ) args
2015-01-30 01:10:49 +00:00
{
2015-02-04 00:15:20 +00:00
NSNumber * moduleID = _moduleIDLookup [ moduleDotMethod ] ;
RCTAssert ( moduleID , @ "Module '%@' not registered." ,
[ [ moduleDotMethod componentsSeparatedByString : @ "." ] firstObject ] ) ;
NSNumber * methodID = _methodIDLookup [ moduleDotMethod ] ;
RCTAssert ( methodID , @ "Method '%@' not registered." , moduleDotMethod ) ;
[ self _invokeAndProcessModule : @ "BatchedBridge"
method : @ "callFunctionReturnFlushedQueue"
arguments : @ [ moduleID , methodID , args ] ] ;
2015-01-30 01:10:49 +00:00
}
- ( void ) enqueueApplicationScript : ( NSString * ) script url : ( NSURL * ) url onComplete : ( RCTJavaScriptCompleteBlock ) onComplete
{
RCTAssert ( onComplete ! = nil , @ "onComplete block passed in should be non-nil" ) ;
[ _javaScriptExecutor executeApplicationScript : script sourceURL : url onComplete : ^ ( NSError * scriptLoadError ) {
if ( scriptLoadError ) {
onComplete ( scriptLoadError ) ;
return ;
}
[ _javaScriptExecutor executeJSCall : @ "BatchedBridge"
method : @ "flushedQueue"
arguments : @ [ ]
callback : ^ ( id objcValue , NSError * error ) {
[ self _handleBuffer : objcValue ] ;
onComplete ( error ) ;
} ] ;
} ] ;
}
# pragma mark - Payload Generation
- ( void ) _invokeAndProcessModule : ( NSString * ) module method : ( NSString * ) method arguments : ( NSArray * ) args
{
NSTimeInterval startJS = RCTTGetAbsoluteTime ( ) ;
RCTJavaScriptCallback processResponse = ^ ( id objcValue , NSError * error ) {
NSTimeInterval startNative = RCTTGetAbsoluteTime ( ) ;
[ self _handleBuffer : objcValue ] ;
NSTimeInterval end = RCTTGetAbsoluteTime ( ) ;
NSTimeInterval timeJS = startNative - startJS ;
NSTimeInterval timeNative = end - startNative ;
// TODO : surface this performance information somewhere
[ [ NSNotificationCenter defaultCenter ] postNotificationName : @ "PERF" object : nil userInfo : @ { @ "JS" : @ ( timeJS * 1000000 ) , @ "Native" : @ ( timeNative * 1000000 ) } ] ;
} ;
[ _javaScriptExecutor executeJSCall : module
method : method
arguments : args
callback : processResponse ] ;
}
/ * *
* TODO ( #5906496 ) : Have responses piggy backed on a round trip with ObjC -> JS requests .
* /
- ( void ) _sendResponseToJavaScriptCallbackID : ( NSInteger ) cbID args : ( NSArray * ) args
{
[ self _invokeAndProcessModule : @ "BatchedBridge"
method : @ "invokeCallbackAndReturnFlushedQueue"
arguments : @ [ @ ( cbID ) , args ] ] ;
}
# pragma mark - Payload Processing
- ( void ) _handleBuffer : ( id ) buffer
{
if ( buffer = = nil || buffer = = ( id ) kCFNull ) {
return ;
}
if ( ! [ buffer isKindOfClass : [ NSArray class ] ] ) {
RCTLogMustFix ( @ "Buffer must be an instance of NSArray, got %@" , NSStringFromClass ( [ buffer class ] ) ) ;
return ;
}
NSArray * requestsArray = ( NSArray * ) buffer ;
NSUInteger bufferRowCount = [ requestsArray count ] ;
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1 ;
if ( bufferRowCount ! = expectedFieldsCount ) {
RCTLogMustFix ( @ "Must pass all fields to buffer - expected %zd, saw %zd" , expectedFieldsCount , bufferRowCount ) ;
return ;
}
for ( NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs ; fieldIndex <= RCTBridgeFieldParamss ; fieldIndex + + ) {
id field = [ requestsArray objectAtIndex : fieldIndex ] ;
if ( ! [ field isKindOfClass : [ NSArray class ] ] ) {
RCTLogMustFix ( @ "Field at index %zd in buffer must be an instance of NSArray, got %@" , fieldIndex , NSStringFromClass ( [ field class ] ) ) ;
return ;
}
}
2015-02-04 00:12:07 +00:00
NSArray * moduleIDs = requestsArray [ RCTBridgeFieldRequestModuleIDs ] ;
NSArray * methodIDs = requestsArray [ RCTBridgeFieldMethodIDs ] ;
NSArray * paramsArrays = requestsArray [ RCTBridgeFieldParamss ] ;
2015-01-30 01:10:49 +00:00
NSUInteger numRequests = [ moduleIDs count ] ;
2015-02-04 00:12:07 +00:00
BOOL allSame = numRequests = = [ methodIDs count ] && numRequests = = [ paramsArrays count ] ;
2015-01-30 01:10:49 +00:00
if ( ! allSame ) {
RCTLogMustFix ( @ "Invalid data message - all must be length: %zd" , numRequests ) ;
return ;
}
for ( NSUInteger i = 0 ; i < numRequests ; i + + ) {
@ autoreleasepool {
[ self _handleRequestNumber : i
2015-02-04 00:12:07 +00:00
moduleID : [ moduleIDs [ i ] integerValue ]
methodID : [ methodIDs [ i ] integerValue ]
params : paramsArrays [ i ] ] ;
2015-01-30 01:10:49 +00:00
}
}
2015-02-04 00:12:07 +00:00
// TODO : only used by RCTUIManager - can we eliminate this special case ?
dispatch_async ( _shadowQueue , ^ {
for ( id target in _moduleInstances . objectEnumerator ) {
if ( [ target respondsToSelector : @ selector ( batchDidComplete ) ] ) {
2015-01-30 01:10:49 +00:00
[ target batchDidComplete ] ;
2015-02-04 00:12:07 +00:00
}
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:12:07 +00:00
} ) ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:12:07 +00:00
- ( BOOL ) _handleRequestNumber : ( NSUInteger ) i
moduleID : ( NSInteger ) moduleID
methodID : ( NSInteger ) methodID
params : ( NSArray * ) params
2015-01-30 01:10:49 +00:00
{
2015-02-04 00:12:07 +00:00
if ( ! [ params isKindOfClass : [ NSArray class ] ] ) {
2015-01-30 01:10:49 +00:00
RCTLogMustFix ( @ "Invalid module/method/params tuple for request #%zd" , i ) ;
2015-02-04 00:12:07 +00:00
return NO ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
if ( moduleID < 0 || moduleID >= RCTExportedMethodsByModule ( ) . count ) {
return NO ;
}
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
NSString * moduleName = RCTExportedModuleNameAtSortedIndex ( moduleID ) ;
NSArray * methods = RCTExportedMethodsByModule ( ) [ moduleName ] ;
if ( methodID < 0 || methodID >= methods . count ) {
return NO ;
}
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
RCTModuleMethod * method = methods [ methodID ] ;
NSUInteger methodArity = method . arity ;
if ( params . count ! = methodArity ) {
2015-02-04 00:12:07 +00:00
RCTLogMustFix ( @ "Expected %tu arguments but got %tu invoking %@.%@" ,
methodArity ,
params . count ,
moduleName ,
method . JSMethodName ) ;
2015-01-30 01:10:49 +00:00
return NO ;
}
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
__weak RCTBridge * weakSelf = self ;
dispatch_async ( _shadowQueue , ^ {
__strong RCTBridge * strongSelf = weakSelf ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
if ( ! strongSelf . isValid ) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue .
return ;
}
2015-02-04 00:15:20 +00:00
2015-01-30 01:10:49 +00:00
// TODO : we should just store module instances by index , since that ' s how we look them up anyway
id target = strongSelf -> _moduleInstances [ moduleName ] ;
RCTAssert ( target ! = nil , @ "No module found for name '%@'" , moduleName ) ;
SEL selector = method . selector ;
2015-02-04 00:15:20 +00:00
NSMethodSignature * methodSignature = [ target methodSignatureForSelector : selector ] ;
NSInvocation * invocation = [ NSInvocation invocationWithMethodSignature : methodSignature ] ;
[ invocation setArgument : & target atIndex : 0 ] ;
2015-01-30 01:10:49 +00:00
[ invocation setArgument : & selector atIndex : 1 ] ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
// Retain used blocks until after invocation completes .
2015-02-04 00:15:20 +00:00
NS_VALID _UNTIL _END _OF _SCOPE NSMutableArray * blocks = [ NSMutableArray array ] ;
2015-01-30 01:10:49 +00:00
[ params enumerateObjectsUsingBlock : ^ ( id param , NSUInteger idx , BOOL * stop ) {
if ( [ param isEqual : [ NSNull null ] ] ) {
param = nil ;
} else if ( [ method . blockArgumentIndexes containsIndex : idx ] ) {
id block = [ strongSelf createResponseSenderBlock : [ param integerValue ] ] ;
[ blocks addObject : block ] ;
param = block ;
}
2015-02-04 00:15:20 +00:00
NSUInteger argIdx = idx + 2 ;
BOOL shouldSet = YES ;
const char * argumentType = [ methodSignature getArgumentTypeAtIndex : argIdx ] ;
switch ( argumentType [ 0 ] ) {
case ' : ' :
if ( [ param isKindOfClass : [ NSString class ] ] ) {
SEL selector = NSSelectorFromString ( param ) ;
[ invocation setArgument : & selector atIndex : argIdx ] ;
shouldSet = NO ;
}
break ;
case ' * ' :
if ( [ param isKindOfClass : [ NSString class ] ] ) {
const char * string = [ param UTF8String ] ;
[ invocation setArgument : & string atIndex : argIdx ] ;
shouldSet = NO ;
}
break ;
# define CASE ( _value , _type , _selector ) \
case _value : \
if ( [ param respondsToSelector : @ selector ( _selector ) ] ) { \
_type value = [ param _selector ] ; \
[ invocation setArgument : & value atIndex : argIdx ] ; \
shouldSet = NO ; \
} \
break ;
CASE ( ' c ' , char , charValue )
CASE ( ' C ' , unsigned char , unsignedCharValue )
CASE ( ' s ' , short , shortValue )
CASE ( ' S ' , unsigned short , unsignedShortValue )
CASE ( ' i ' , int , intValue )
CASE ( ' I ' , unsigned int , unsignedIntValue )
CASE ( ' l ' , long , longValue )
CASE ( ' L ' , unsigned long , unsignedLongValue )
CASE ( ' q ' , long long , longLongValue )
CASE ( ' Q ' , unsigned long long , unsignedLongLongValue )
CASE ( ' f ' , float , floatValue )
CASE ( ' d ' , double , doubleValue )
CASE ( ' B ' , BOOL , boolValue )
default :
break ;
}
if ( shouldSet ) {
[ invocation setArgument : & param atIndex : argIdx ] ;
}
2015-01-30 01:10:49 +00:00
} ] ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
@ try {
[ invocation invoke ] ;
}
@ catch ( NSException * exception ) {
RCTLogMustFix ( @ "Exception thrown while invoking %@ on target %@ with params %@: %@" , method . JSMethodName , target , params , exception ) ;
}
} ) ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
return YES ;
}
2015-02-04 00:12:07 +00:00
/ * *
* Returns a callback that reports values back to the JS thread .
* TODO ( #5906496 ) : These responses should go into their own queue ` MessageQueue . m` that
* mirrors the JS queue and protocol . For now , we speak the "language" of the JS
* queue by packing it into an array that matches the wire protocol .
* /
- ( RCTResponseSenderBlock ) createResponseSenderBlock : ( NSInteger ) cbID
{
if ( ! cbID ) {
return nil ;
}
return ^ ( NSArray * args ) {
[ self _sendResponseToJavaScriptCallbackID : cbID args : args ] ;
} ;
}
+ ( NSInvocation * ) invocationForAdditionalArguments : ( NSUInteger ) argCount
{
static NSMutableDictionary * invocations ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
invocations = [ NSMutableDictionary dictionary ] ;
} ) ;
id key = @ ( argCount ) ;
NSInvocation * invocation = invocations [ key ] ;
if ( invocation = = nil ) {
NSString * objCTypes = [ @ "v@:" stringByPaddingToLength : 3 + argCount withString : @ "@" startingAtIndex : 0 ] ;
NSMethodSignature * methodSignature = [ NSMethodSignature signatureWithObjCTypes : objCTypes . UTF8String ] ;
invocation = [ NSInvocation invocationWithMethodSignature : methodSignature ] ;
invocations [ key ] = invocation ;
}
return invocation ;
}
2015-02-04 00:15:20 +00:00
- ( NSArray * ) JSMethods
{
NSMutableArray * methods = [ [ NSMutableArray alloc ] init ] ;
// Add globally used methods
[ methods addObjectsFromArray : @ [
@ "Bundler.runApplication" ,
@ "RCTEventEmitter.receiveEvent" ,
@ "RCTEventEmitter.receiveTouches" ,
] ] ;
// NOTE : these methods are currently unused in the OSS project
// @ "Dimensions.set" ,
// @ "RCTDeviceEventEmitter.emit" ,
// @ "RCTNativeAppEventEmitter.emit" ,
// @ "ReactIOS.unmountComponentAtNodeAndRemoveContainer" ,
// Register individual methods from modules
for ( Class cls in RCTNativeModuleClasses ( ) . allValues ) {
if ( RCTClassOverridesClassMethod ( cls , @ selector ( JSMethods ) ) ) {
[ methods addObjectsFromArray : [ cls JSMethods ] ] ;
}
}
return methods ;
}
2015-01-30 01:10:49 +00:00
- ( void ) doneRegisteringModules
{
2015-02-04 00:15:20 +00:00
// TODO : everything in this method can actually be determined
// statically from just the class , and can therefore be cached
// in a dispatch_once instead of being repeated every time we
// reload or create a new RootView .
2015-01-30 01:10:49 +00:00
RCTAssertMainThread ( ) ;
2015-02-04 00:15:20 +00:00
/ * *
* This constructs the remote modules configuration data structure ,
* which represents the native modules and methods that will be called
* by JS . A numeric ID is assigned to each module and method , which will
* be used to communicate via the bridge . The structure of each
* module is as follows :
*
* "ModuleName1" : {
* "moduleID" : 0 ,
* "methods" : {
* "methodName1" : {
* "methodID" : 0 ,
* "type" : "remote"
* } ,
* "methodName2" : {
* "methodID" : 1 ,
* "type" : "remote"
* } ,
* etc . . .
* } ,
* "constants" : {
* . . .
* }
* } ,
* etc . . .
* /
2015-01-30 01:10:49 +00:00
NSUInteger moduleCount = RCTExportedMethodsByModule ( ) . count ;
2015-02-04 00:15:20 +00:00
NSMutableDictionary * remoteModules = [ NSMutableDictionary dictionaryWithCapacity : RCTExportedMethodsByModule ( ) . count ] ;
2015-01-30 01:10:49 +00:00
for ( NSUInteger i = 0 ; i < moduleCount ; i + + ) {
NSString * moduleName = RCTExportedModuleNameAtSortedIndex ( i ) ;
NSArray * rawMethods = RCTExportedMethodsByModule ( ) [ moduleName ] ;
NSMutableDictionary * methods = [ NSMutableDictionary dictionaryWithCapacity : rawMethods . count ] ;
[ rawMethods enumerateObjectsUsingBlock : ^ ( RCTModuleMethod * method , NSUInteger methodID , BOOL * stop ) {
methods [ method . JSMethodName ] = @ {
@ "methodID" : @ ( methodID ) ,
@ "type" : @ "remote" ,
} ;
} ] ;
2015-02-04 00:15:20 +00:00
NSDictionary * module = @ {
@ "moduleID" : @ ( i ) ,
@ "methods" : methods
} ;
2015-01-30 01:10:49 +00:00
2015-02-04 00:15:20 +00:00
id target = _moduleInstances [ moduleName ] ;
if ( RCTClassOverridesClassMethod ( [ target class ] , @ selector ( constantsToExport ) ) ) {
module = [ module mutableCopy ] ;
( ( NSMutableDictionary * ) module ) [ @ "constants" ] = [ [ target class ] constantsToExport ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
remoteModules [ moduleName ] = module ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
/ * *
* As above , but for local modules / methods , which represent JS classes
* and methods that will be called by the native code via the bridge .
* Structure is essentially the same as for remote modules :
*
* "ModuleName1" : {
* "moduleID" : 0 ,
* "methods" : {
* "methodName1" : {
* "methodID" : 0 ,
* "type" : "local"
* } ,
* "methodName2" : {
* "methodID" : 1 ,
* "type" : "local"
* } ,
* etc . . .
* }
* } ,
* etc . . .
* /
NSMutableDictionary * localModules = [ [ NSMutableDictionary alloc ] init ] ;
for ( NSString * moduleDotMethod in [ self JSMethods ] ) {
NSArray * parts = [ moduleDotMethod componentsSeparatedByString : @ "." ] ;
RCTAssert ( 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 ] ;
if ( ! module ) {
module = @ {
@ "moduleID" : @ ( localModules . count ) ,
@ "methods" : [ [ NSMutableDictionary alloc ] init ]
} ;
localModules [ moduleName ] = module ;
}
// Add method if it doesn ' t already exist
NSString * methodName = parts [ 1 ] ;
NSMutableDictionary * methods = module [ @ "methods" ] ;
if ( ! methods [ methodName ] ) {
methods [ methodName ] = @ {
@ "methodID" : @ ( methods . count ) ,
@ "type" : @ "local"
} ;
}
// Add module and method lookup
_moduleIDLookup [ moduleDotMethod ] = module [ @ "moduleID" ] ;
_methodIDLookup [ moduleDotMethod ] = methods [ methodName ] [ @ "methodID" ] ;
}
/ * *
* Inject module data into JS context
* /
NSString * configJSON = RCTJSONStringify ( @ {
@ "remoteModuleConfig" : remoteModules ,
@ "localModulesConfig" : localModules
} , NULL ) ;
2015-01-30 01:10:49 +00:00
dispatch_semaphore _t semaphore = dispatch_semaphore _create ( 0 ) ;
2015-02-04 00:15:20 +00:00
[ _javaScriptExecutor injectJSONText : configJSON asGlobalObjectNamed : @ "__fbBatchedBridgeConfig" callback : ^ ( id err ) {
dispatch_semaphore _signal ( semaphore ) ;
2015-01-30 01:10:49 +00:00
} ] ;
2015-02-04 00:15:20 +00:00
if ( dispatch_semaphore _wait ( semaphore , dispatch_time ( DISPATCH_TIME _NOW , NSEC_PER _SEC ) ) ! = 0 ) {
RCTLogMustFix ( @ "JavaScriptExecutor take too long to inject JSON object" ) ;
2015-01-30 01:10:49 +00:00
}
}
2015-02-04 00:12:07 +00:00
- ( void ) registerRootView : ( RCTRootView * ) rootView
{
// TODO : only used by RCTUIManager - can we eliminate this special case ?
for ( id target in _moduleInstances . objectEnumerator ) {
if ( [ target respondsToSelector : @ selector ( registerRootView : ) ] ) {
[ target registerRootView : rootView ] ;
}
}
}
2015-01-30 01:10:49 +00:00
+ ( BOOL ) hasValidJSExecutor
{
return ( _latestJSExecutor ! = nil && [ _latestJSExecutor isValid ] ) ;
}
+ ( void ) log : ( NSArray * ) objects level : ( NSString * ) level
{
if ( ! _latestJSExecutor || ! [ _latestJSExecutor isValid ] ) {
RCTLogError ( @ "%@" , RCTLogFormatString ( @ "ERROR: No valid JS executor to log %@." , objects ) ) ;
return ;
}
NSMutableArray * args = [ NSMutableArray arrayWithObject : level ] ;
// TODO ( #5906496 ) : Find out and document why we skip the first object
for ( id ob in [ objects subarrayWithRange : ( NSRange ) { 1 , [ objects count ] - 1 } ] ) {
if ( [ NSJSONSerialization isValidJSONObject : @ [ ob ] ] ) {
[ args addObject : ob ] ;
} else {
[ args addObject : [ ob description ] ] ;
}
}
// Note the js executor could get invalidated while we ' re trying to call this . . . need to watch out for that .
[ _latestJSExecutor executeJSCall : @ "RCTLog"
method : @ "logIfNoNativeHook"
arguments : args
callback : ^ ( id objcValue , NSError * error ) { } ] ;
}
@ end