diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index cf305739a..3493efac3 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -14,8 +14,8 @@ #import "RCTBridge+Private.h" #import "RCTBridgeMethod.h" #import "RCTConvert.h" +#import "RCTDisplayLink.h" #import "RCTJSCExecutor.h" -#import "RCTFrameUpdate.h" #import "RCTJavaScriptLoader.h" #import "RCTLog.h" #import "RCTModuleData.h" @@ -56,8 +56,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); NSMutableDictionary *_moduleDataByName; NSArray *_moduleDataByID; NSArray *_moduleClassesByID; - CADisplayLink *_jsDisplayLink; - NSMutableSet *_frameUpdateObservers; + RCTDisplayLink *_displayLink; } @synthesize flowID = _flowID; @@ -81,8 +80,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); _valid = YES; _loading = YES; _pendingCalls = [NSMutableArray new]; - _frameUpdateObservers = [NSMutableSet new]; - _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; + _displayLink = [RCTDisplayLink new]; [RCTBridge setCurrentBridge:self]; @@ -409,20 +407,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData { - if (![_frameUpdateObservers containsObject:moduleData]) { - if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { - [_frameUpdateObservers addObject:moduleData]; - // Don't access the module instance via moduleData, as this will cause deadlock - id observer = (id)module; - __weak typeof(self) weakSelf = self; - __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; - observer.pauseCallback = ^{ - [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ - [weakSelf updateJSDisplayLinkState]; - }]; - }; - } - } + [_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData]; } - (NSString *)moduleConfig @@ -441,21 +426,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); }, NULL); } -- (void)updateJSDisplayLinkState -{ - RCTAssertJSThread(); - - BOOL pauseDisplayLink = YES; - for (RCTModuleData *moduleData in _frameUpdateObservers) { - id observer = (id)moduleData.instance; - if (!observer.paused) { - pauseDisplayLink = NO; - break; - } - } - _jsDisplayLink.paused = pauseDisplayLink; -} - - (void)injectJSONConfiguration:(NSString *)configJSON onComplete:(void (^)(NSError *))onComplete { @@ -492,7 +462,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); // Register the display link to start sending js calls after everything is setup NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; - [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes]; + [_displayLink addToRunLoop:targetRunLoop]; // Perform the state update and notification on the main thread, so we can't run into // timing issues with RCTRootView @@ -643,8 +613,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR dispatch_group_notify(group, dispatch_get_main_queue(), ^{ [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - [_jsDisplayLink invalidate]; - _jsDisplayLink = nil; + [_displayLink invalidate]; + _displayLink = nil; [_javaScriptExecutor invalidate]; _javaScriptExecutor = nil; @@ -656,7 +626,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR _moduleDataByName = nil; _moduleDataByID = nil; _moduleClassesByID = nil; - _frameUpdateObservers = nil; _pendingCalls = nil; if (_flowIDMap != NULL) { @@ -1010,30 +979,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR return YES; } -- (void)_jsThreadUpdate:(CADisplayLink *)displayLink -{ - RCTAssertJSThread(); - RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge _jsThreadUpdate:]", nil); - - RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; - for (RCTModuleData *moduleData in _frameUpdateObservers) { - id observer = (id)moduleData.instance; - if (!observer.paused) { - RCTProfileBeginFlowEvent(); - [self dispatchBlock:^{ - RCTProfileEndFlowEvent(); - [observer didUpdateFrame:frameUpdate]; - } queue:moduleData.methodQueue]; - } - } - - [self updateJSDisplayLinkState]; - - RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g'); - - RCT_PROFILE_END_EVENT(0, @"objc_call", nil); -} - - (void)startProfiling { RCTAssertMainThread(); diff --git a/React/Base/RCTDisplayLink.h b/React/Base/RCTDisplayLink.h new file mode 100644 index 000000000..d4de929c0 --- /dev/null +++ b/React/Base/RCTDisplayLink.h @@ -0,0 +1,23 @@ +/** + * 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 + +@protocol RCTBridgeModule; +@class RCTModuleData; + +@interface RCTDisplayLink : NSObject + +- (instancetype)init; +- (void)invalidate; +- (void)registerModuleForFrameUpdates:(id)module + withModuleData:(RCTModuleData *)moduleData; +- (void)addToRunLoop:(NSRunLoop *)runLoop; + +@end diff --git a/React/Base/RCTDisplayLink.m b/React/Base/RCTDisplayLink.m new file mode 100644 index 000000000..8c7c80820 --- /dev/null +++ b/React/Base/RCTDisplayLink.m @@ -0,0 +1,137 @@ +/** + * 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 "RCTDisplayLink.h" + +#import +#import + +#import "RCTAssert.h" +#import "RCTBridgeModule.h" +#import "RCTFrameUpdate.h" +#import "RCTModuleData.h" +#import "RCTProfile.h" + +@implementation RCTDisplayLink +{ + CADisplayLink *_jsDisplayLink; + NSMutableSet *_frameUpdateObservers; + NSRunLoop *_runLoop; +} + +- (instancetype)init +{ + if ((self = [super init])) { + _frameUpdateObservers = [NSMutableSet new]; + _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; + } + + return self; +} + +- (void)registerModuleForFrameUpdates:(id)module + withModuleData:(RCTModuleData *)moduleData +{ + if ([_frameUpdateObservers containsObject:moduleData] || + ![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + return; + } + + [_frameUpdateObservers addObject:moduleData]; + // Don't access the module instance via moduleData, as this will cause deadlock + id observer = (id)module; + __weak typeof(self) weakSelf = self; + observer.pauseCallback = ^{ + typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop]; + + if (!_runLoop) { + return; + } + + CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{ + [weakSelf updateJSDisplayLinkState]; + }); + CFRunLoopWakeUp(cfRunLoop); + }; +} + +- (void)addToRunLoop:(NSRunLoop *)runLoop +{ + _runLoop = runLoop; + [_jsDisplayLink addToRunLoop:runLoop forMode:NSRunLoopCommonModes]; +} + +- (void)invalidate +{ + [_jsDisplayLink invalidate]; +} + +- (void)assertOnRunLoop +{ + RCTAssert(_runLoop == [NSRunLoop currentRunLoop], + @"This method must be called on the CADisplayLink run loop"); +} + +- (void)dispatchBlock:(dispatch_block_t)block + queue:(dispatch_queue_t)queue +{ + if (queue == RCTJSThread) { + block(); + } else if (queue) { + dispatch_async(queue, block); + } +} + +- (void)_jsThreadUpdate:(CADisplayLink *)displayLink +{ + [self assertOnRunLoop]; + + RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTDisplayLink _jsThreadUpdate:]", nil); + + RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; + for (RCTModuleData *moduleData in _frameUpdateObservers) { + id observer = (id)moduleData.instance; + if (!observer.paused) { + RCTProfileBeginFlowEvent(); + + [self dispatchBlock:^{ + RCTProfileEndFlowEvent(); + [observer didUpdateFrame:frameUpdate]; + } queue:moduleData.methodQueue]; + } + } + + [self updateJSDisplayLinkState]; + + RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g'); + + RCT_PROFILE_END_EVENT(0, @"objc_call", nil); +} + +- (void)updateJSDisplayLinkState +{ + [self assertOnRunLoop]; + + BOOL pauseDisplayLink = YES; + for (RCTModuleData *moduleData in _frameUpdateObservers) { + id observer = (id)moduleData.instance; + if (!observer.paused) { + pauseDisplayLink = NO; + break; + } + } + _jsDisplayLink.paused = pauseDisplayLink; +} + +@end