diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 48fd672a3..67c86fb12 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -31,6 +31,8 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; +dispatch_queue_t const RCTJSThread = nil; + /** * Must be kept in sync with `MessageQueue.js`. */ @@ -795,6 +797,7 @@ static id _latestJSExecutor; _bundleURL = bundleURL; _moduleProvider = block; _launchOptions = [launchOptions copy]; + [self setUp]; [self bindKeys]; } @@ -872,6 +875,8 @@ static id _latestJSExecutor; dispatch_queue_t queue = [module methodQueue]; if (queue) { _queuesByID[moduleID] = queue; + } else { + _queuesByID[moduleID] = [NSNull null]; } } }]; @@ -1128,6 +1133,16 @@ static id _latestJSExecutor; #pragma mark - Payload Generation +- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID +{ + id queue = _queuesByID[moduleID]; + if (queue == [NSNull null]) { + [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; + } else { + dispatch_async(queue ?: _methodQueue, block); + } +} + - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context { #if BATCHED_BRIDGE @@ -1235,10 +1250,9 @@ static id _latestJSExecutor; // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { if ([module respondsToSelector:@selector(batchDidComplete)]) { - dispatch_queue_t queue = _queuesByID[moduleID]; - dispatch_async(queue ?: _methodQueue, ^{ + [self dispatchBlock:^{ [module batchDidComplete]; - }); + } forModule:moduleID]; } }]; } @@ -1273,8 +1287,7 @@ static id _latestJSExecutor; } __weak RCTBridge *weakSelf = self; - dispatch_queue_t queue = _queuesByID[moduleID]; - dispatch_async(queue ?: _methodQueue, ^{ + [self dispatchBlock:^{ RCTProfileBeginEvent(); __strong RCTBridge *strongSelf = weakSelf; @@ -1303,7 +1316,7 @@ static id _latestJSExecutor; @"method": method.JSMethodName, @"selector": NSStringFromSelector(method.selector), }); - }); + } forModule:@(moduleID)]; return YES; } diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index dd6f61e23..34b861ff3 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -17,6 +17,16 @@ */ typedef void (^RCTResponseSenderBlock)(NSArray *response); +/** + * This constant can be returned from +methodQueue to force module + * methods to be called on the JavaScript thread. This can have serious + * implications for performance, so only use this if you're sure it's what + * you need. + * + * NOTE: RCTJSThread is not a real libdispatch queue + */ +extern const dispatch_queue_t RCTJSThread; + /** * Provides the interface needed to register a bridge module. */ diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 48d371b7e..edcd2aebd 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -307,12 +307,17 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { - if ([NSThread currentThread] != _javaScriptThread) { - [self performSelector:@selector(executeBlockOnJavaScriptQueue:) - onThread:_javaScriptThread withObject:block waitUntilDone:NO]; - } else { - block(); - } + /** + * Always dispatch async, ensure there are no sync calls on the JS thread + * otherwise timers can cause a deadlock + */ + [self performSelector:@selector(_runBlock:) + onThread:_javaScriptThread withObject:block waitUntilDone:NO]; +} + +- (void)_runBlock:(dispatch_block_t)block +{ + block(); } - (void)injectJSONText:(NSString *)script diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 1d99c1a2d..e2df5befc 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -110,7 +110,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) - (dispatch_queue_t)methodQueue { - return dispatch_get_main_queue(); + return RCTJSThread; } - (BOOL)isValid @@ -131,8 +131,6 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) - (void)startTimers { - RCTAssertMainThread(); - if (![self isValid] || _timers.count == 0) { return; }