diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index eb6428fc2..753c8d76d 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -175,6 +175,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); }); } +- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block +{ + dispatch_async(dispatch_get_main_queue(), block); +} + - (void)invalidate { _socket.delegate = nil; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 1c676640e..a4a4362b0 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -694,7 +694,7 @@ static NSDictionary *RCTLocalModulesConfig() @interface RCTDisplayLink : NSObject -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER; @end @@ -708,14 +708,16 @@ static NSDictionary *RCTLocalModulesConfig() { __weak RCTBridge *_bridge; CADisplayLink *_displayLink; + SEL _selector; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector { if ((self = [super init])) { _bridge = bridge; + _selector = selector; _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } return self; } @@ -735,7 +737,10 @@ static NSDictionary *RCTLocalModulesConfig() - (void)_update:(CADisplayLink *)displayLink { - [_bridge _update:displayLink]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [_bridge performSelector:_selector withObject:displayLink]; +#pragma clang diagnostic pop } @end @@ -770,6 +775,7 @@ static NSDictionary *RCTLocalModulesConfig() NSURL *_bundleURL; RCTBridgeModuleProviderBlock _moduleProvider; RCTDisplayLink *_displayLink; + RCTDisplayLink *_vsyncDisplayLink; NSMutableSet *_frameUpdateObservers; NSMutableArray *_scheduledCalls; RCTSparseArray *_scheduledCallbacks; @@ -799,11 +805,15 @@ static id _latestJSExecutor; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL); - _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; _scheduledCallbacks = [[RCTSparseArray alloc] init]; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + _displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)]; + }]; + _vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)]; + // Register passed-in module instances NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; for (id module in _moduleProvider ? _moduleProvider() : nil) { @@ -1008,6 +1018,7 @@ static id _latestJSExecutor; _javaScriptExecutor = nil; [_displayLink invalidate]; + [_vsyncDisplayLink invalidate]; _frameUpdateObservers = nil; // Invalidate modules @@ -1294,9 +1305,9 @@ static id _latestJSExecutor; return YES; } -- (void)_update:(CADisplayLink *)displayLink +- (void)_jsThreadUpdate:(CADisplayLink *)displayLink { - RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); RCTProfileBeginEvent(); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; @@ -1306,13 +1317,6 @@ static id _latestJSExecutor; } } - [self _runScheduledCalls]; - - RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); -} - -- (void)_runScheduledCalls -{ #if BATCHED_BRIDGE NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; @@ -1330,6 +1334,13 @@ static id _latestJSExecutor; } #endif + + RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); +} + +- (void)_mainThreadUpdate:(CADisplayLink *)displayLink +{ + RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); } - (void)addFrameUpdateObserver:(id)observer diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 8ff5a1658..eb7fd7d31 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -42,6 +42,13 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete; + +/** + * Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async` + * on the main queue if the executor doesn't own a thread. + */ +- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; + @end static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 929a026a9..19c6900c7 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -36,6 +36,7 @@ NSDictionary *RCTProfileInfo; NSUInteger RCTProfileEventID = 0; NSMutableDictionary *RCTProfileOngoingEvents; NSTimeInterval RCTProfileStartTime; +NSLock *_RCTProfileLock; #pragma mark - Macros @@ -51,6 +52,11 @@ if (!RCTProfileIsProfiling()) { \ return __VA_ARGS__; \ } +#define RCTProfileLock(...) \ +[_RCTProfileLock lock]; \ +__VA_ARGS__ \ +[_RCTProfileLock unlock] + #pragma mark - Private Helpers NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) @@ -66,7 +72,6 @@ NSString *RCTProfileMemory(vm_size_t memory) NSDictionary *RCTProfileGetMemoryUsage(void) { - CHECK(@{}); struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), @@ -88,66 +93,81 @@ NSDictionary *RCTProfileGetMemoryUsage(void) BOOL RCTProfileIsProfiling(void) { - return RCTProfileInfo != nil; + RCTProfileLock( + BOOL profiling = RCTProfileInfo != nil; + ); + return profiling; } void RCTProfileInit(void) { - RCTProfileStartTime = CACurrentMediaTime(); - RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; - RCTProfileInfo = @{ - RCTProfileTraceEvents: [[NSMutableArray alloc] init], - RCTProfileSamples: [[NSMutableArray alloc] init], - }; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _RCTProfileLock = [[NSLock alloc] init]; + }); + RCTProfileLock( + RCTProfileStartTime = CACurrentMediaTime(); + RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; + RCTProfileInfo = @{ + RCTProfileTraceEvents: [[NSMutableArray alloc] init], + RCTProfileSamples: [[NSMutableArray alloc] init], + }; + ); } NSString *RCTProfileEnd(void) { - NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); - RCTProfileEventID = 0; - RCTProfileInfo = nil; - RCTProfileOngoingEvents = nil; + RCTProfileLock( + NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); + RCTProfileEventID = 0; + RCTProfileInfo = nil; + RCTProfileOngoingEvents = nil; + ); return log; } NSNumber *_RCTProfileBeginEvent(void) { CHECK(@0); - NSNumber *eventID = @(++RCTProfileEventID); - RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); + RCTProfileLock( + NSNumber *eventID = @(++RCTProfileEventID); + RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); + ); return eventID; } void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args) { CHECK(); - NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; - if (!startTimestamp) { - return; - } + RCTProfileLock( + NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; + if (startTimestamp) { + NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); - NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); - - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"cat": categories, - @"ph": @"X", - @"ts": startTimestamp, - @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), - @"args": args ?: @[], + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"cat": categories, + @"ph": @"X", + @"ts": startTimestamp, + @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), + @"args": args ?: @[], + ); + [RCTProfileOngoingEvents removeObjectForKey:eventID]; + } ); - [RCTProfileOngoingEvents removeObjectForKey:eventID]; } void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope) { CHECK(); - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"ts": RCTProfileTimestamp(timestamp), - @"scope": scope, - @"ph": @"i", - @"args": RCTProfileGetMemoryUsage(), + RCTProfileLock( + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"ts": RCTProfileTimestamp(timestamp), + @"scope": scope, + @"ph": @"i", + @"args": RCTProfileGetMemoryUsage(), + ); ); } diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 62d42a7bb..1d99c1a2d 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -142,8 +142,6 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) - (void)didUpdateFrame:(RCTFrameUpdate *)update { - RCTAssertMainThread(); - NSMutableArray *timersToCall = [[NSMutableArray alloc] init]; for (RCTTimer *timer in _timers.allObjects) { if ([timer updateFoundNeedsJSUpdate]) {