[ReactNative] Send batched calls from objc to js every frame + add bridge profiling
This commit is contained in:
parent
70a28547dd
commit
1883ba535c
|
@ -74,7 +74,8 @@ var BatchedBridgeFactory = {
|
||||||
setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue),
|
setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue),
|
||||||
getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue),
|
getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue),
|
||||||
getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue),
|
getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue),
|
||||||
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue)
|
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue),
|
||||||
|
processBatch: messageQueue.processBatch.bind(messageQueue),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -307,6 +307,25 @@ var MessageQueueMixin = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
processBatch: function (batch) {
|
||||||
|
var self = this;
|
||||||
|
batch.forEach(function (call) {
|
||||||
|
invariant(
|
||||||
|
call.module === 'BatchedBridge',
|
||||||
|
'All the calls should pass through the BatchedBridge module'
|
||||||
|
);
|
||||||
|
if (call.method === 'callFunctionReturnFlushedQueue') {
|
||||||
|
self.callFunction.apply(self, call.args);
|
||||||
|
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
|
||||||
|
self.invokeCallback.apply(self, call.args);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Unrecognized method called on BatchedBridge: ' + call.method);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.flushedQueue();
|
||||||
|
},
|
||||||
|
|
||||||
setLoggingEnabled: function(enabled) {
|
setLoggingEnabled: function(enabled) {
|
||||||
this._enableLogging = enabled;
|
this._enableLogging = enabled;
|
||||||
this._loggedIncomingItems = [];
|
this._loggedIncomingItems = [];
|
||||||
|
|
|
@ -42,6 +42,45 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||||
RCTBridgeFieldFlushDateMillis
|
RCTBridgeFieldFlushDateMillis
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporarily allow to turn on and off the call batching in case someone wants
|
||||||
|
* to profile both
|
||||||
|
*/
|
||||||
|
#define BATCHED_BRIDGE 1
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#define RCT_PROFILE_START() \
|
||||||
|
_Pragma("clang diagnostic push") \
|
||||||
|
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||||
|
NSTimeInterval __rct_profile_start = CACurrentMediaTime() \
|
||||||
|
_Pragma("clang diagnostic pop")
|
||||||
|
|
||||||
|
#define RCT_PROFILE_END(cat, args, profileName...) \
|
||||||
|
do { \
|
||||||
|
if (_profile) { \
|
||||||
|
[_profileLock lock]; \
|
||||||
|
[_profile addObject:@{ \
|
||||||
|
@"name": [@[profileName] componentsJoinedByString: @"_"], \
|
||||||
|
@"cat": @ #cat, \
|
||||||
|
@"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \
|
||||||
|
@"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \
|
||||||
|
@"ph": @"X", \
|
||||||
|
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
|
||||||
|
@"tid": [[NSThread currentThread] description], \
|
||||||
|
@"args": args ?: [NSNull null], \
|
||||||
|
}]; \
|
||||||
|
[_profileLock unlock]; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define RCT_PROFILE_START(...)
|
||||||
|
#define RCT_PROFILE_END(...)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __LP64__
|
#ifdef __LP64__
|
||||||
typedef uint64_t RCTHeaderValue;
|
typedef uint64_t RCTHeaderValue;
|
||||||
typedef struct section_64 RCTHeaderSection;
|
typedef struct section_64 RCTHeaderSection;
|
||||||
|
@ -191,10 +230,16 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||||
|
|
||||||
@interface RCTBridge ()
|
@interface RCTBridge ()
|
||||||
|
|
||||||
|
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||||
|
|
||||||
- (void)_invokeAndProcessModule:(NSString *)module
|
- (void)_invokeAndProcessModule:(NSString *)module
|
||||||
method:(NSString *)method
|
method:(NSString *)method
|
||||||
arguments:(NSArray *)args;
|
arguments:(NSArray *)args;
|
||||||
|
|
||||||
|
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
|
||||||
|
method:(NSString *)method
|
||||||
|
arguments:(NSArray *)args;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -754,7 +799,13 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||||
RCTDisplayLink *_displayLink;
|
RCTDisplayLink *_displayLink;
|
||||||
NSMutableSet *_frameUpdateObservers;
|
NSMutableSet *_frameUpdateObservers;
|
||||||
|
NSMutableArray *_scheduledCalls;
|
||||||
|
NSMutableArray *_scheduledCallbacks;
|
||||||
BOOL _loading;
|
BOOL _loading;
|
||||||
|
|
||||||
|
NSUInteger _startingTime;
|
||||||
|
NSMutableArray *_profile;
|
||||||
|
NSLock *_profileLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
|
@ -782,6 +833,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||||
|
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||||
|
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
// Register passed-in module instances
|
// Register passed-in module instances
|
||||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||||
|
@ -1005,20 +1058,54 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private hack to support `setTimeout(fn, 0)`
|
||||||
|
*/
|
||||||
|
- (void)_immediatelyCallTimer:(NSNumber *)timer
|
||||||
|
{
|
||||||
|
NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
|
||||||
|
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
|
||||||
|
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
|
||||||
|
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
|
||||||
|
|
||||||
|
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||||
|
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||||
|
|
||||||
|
if (!_loading) {
|
||||||
|
#if BATCHED_BRIDGE
|
||||||
|
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||||
|
method:@"callFunctionReturnFlushedQueue"
|
||||||
|
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||||
|
method:@"callFunctionReturnFlushedQueue"
|
||||||
|
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||||
{
|
{
|
||||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||||
|
RCT_PROFILE_START();
|
||||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||||
|
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
|
||||||
if (scriptLoadError) {
|
if (scriptLoadError) {
|
||||||
onComplete(scriptLoadError);
|
onComplete(scriptLoadError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RCT_PROFILE_START();
|
||||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||||
method:@"flushedQueue"
|
method:@"flushedQueue"
|
||||||
arguments:@[]
|
arguments:@[]
|
||||||
callback:^(id json, NSError *error) {
|
callback:^(id json, NSError *error) {
|
||||||
|
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
|
||||||
|
RCT_PROFILE_START();
|
||||||
[self _handleBuffer:json];
|
[self _handleBuffer:json];
|
||||||
|
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||||
onComplete(error);
|
onComplete(error);
|
||||||
}];
|
}];
|
||||||
}];
|
}];
|
||||||
|
@ -1028,11 +1115,46 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
|
|
||||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||||
{
|
{
|
||||||
|
#if BATCHED_BRIDGE
|
||||||
|
RCT_PROFILE_START();
|
||||||
|
|
||||||
|
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||||
|
for (NSDictionary *call in _scheduledCalls) {
|
||||||
|
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
|
||||||
|
[_scheduledCalls removeObject:call];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id call = @{
|
||||||
|
@"module": module,
|
||||||
|
@"method": method,
|
||||||
|
@"args": args,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||||
|
[_scheduledCallbacks addObject:call];
|
||||||
|
} else {
|
||||||
|
[_scheduledCalls addObject:call];
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||||
|
{
|
||||||
|
#endif
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
||||||
|
|
||||||
|
NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method];
|
||||||
|
RCT_PROFILE_START();
|
||||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
||||||
|
RCT_PROFILE_END(js_call, args, moduleDotMethod);
|
||||||
|
|
||||||
|
RCT_PROFILE_START();
|
||||||
[self _handleBuffer:json];
|
[self _handleBuffer:json];
|
||||||
|
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||||
};
|
};
|
||||||
|
|
||||||
[_javaScriptExecutor executeJSCall:module
|
[_javaScriptExecutor executeJSCall:module
|
||||||
|
@ -1151,12 +1273,34 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
|
|
||||||
- (void)_update:(CADisplayLink *)displayLink
|
- (void)_update:(CADisplayLink *)displayLink
|
||||||
{
|
{
|
||||||
|
RCT_PROFILE_START();
|
||||||
|
|
||||||
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
||||||
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
|
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
|
||||||
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
||||||
[observer didUpdateFrame:frameUpdate];
|
[observer didUpdateFrame:frameUpdate];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[self _runScheduledCalls];
|
||||||
|
|
||||||
|
RCT_PROFILE_END(display_link, nil, @"main_thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_runScheduledCalls
|
||||||
|
{
|
||||||
|
#if BATCHED_BRIDGE
|
||||||
|
|
||||||
|
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||||
|
if (calls.count > 0) {
|
||||||
|
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||||
|
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||||
|
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||||
|
method:@"processBatch"
|
||||||
|
arguments:@[calls]];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
||||||
|
@ -1194,4 +1338,42 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
callback:^(id json, NSError *error) {}];
|
callback:^(id json, NSError *error) {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)startProfiling
|
||||||
|
{
|
||||||
|
if (![_bundleURL.scheme isEqualToString:@"http"]) {
|
||||||
|
RCTLogError(@"To run the profiler you must be running from the dev server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_profileLock = [[NSLock alloc] init];
|
||||||
|
_startingTime = CACurrentMediaTime();
|
||||||
|
|
||||||
|
[_profileLock lock];
|
||||||
|
_profile = [[NSMutableArray alloc] init];
|
||||||
|
[_profileLock unlock];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopProfiling
|
||||||
|
{
|
||||||
|
[_profileLock lock];
|
||||||
|
NSArray *profile = _profile;
|
||||||
|
_profile = nil;
|
||||||
|
[_profileLock unlock];
|
||||||
|
_profileLock = nil;
|
||||||
|
|
||||||
|
NSString *log = RCTJSONStringify(profile, NULL);
|
||||||
|
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
|
||||||
|
NSURL *URL = [NSURL URLWithString:URLString];
|
||||||
|
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||||
|
URLRequest.HTTPMethod = @"POST";
|
||||||
|
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||||
|
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
||||||
|
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
if (error) {
|
||||||
|
RCTLogError(@"%@", error.localizedDescription);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -14,6 +14,15 @@
|
||||||
#import "RCTSourceCode.h"
|
#import "RCTSourceCode.h"
|
||||||
#import "RCTWebViewExecutor.h"
|
#import "RCTWebViewExecutor.h"
|
||||||
|
|
||||||
|
@interface RCTBridge (RCTDevMenu)
|
||||||
|
|
||||||
|
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||||
|
|
||||||
|
- (void)startProfiling;
|
||||||
|
- (void)stopProfiling;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface RCTDevMenu () <UIActionSheetDelegate>
|
@interface RCTDevMenu () <UIActionSheetDelegate>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -37,11 +46,12 @@
|
||||||
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||||
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||||
|
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
|
||||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||||
delegate:self
|
delegate:self
|
||||||
cancelButtonTitle:@"Cancel"
|
cancelButtonTitle:@"Cancel"
|
||||||
destructiveButtonTitle:nil
|
destructiveButtonTitle:nil
|
||||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, nil];
|
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||||
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
||||||
}
|
}
|
||||||
|
@ -61,6 +71,12 @@
|
||||||
} else if (buttonIndex == 3) {
|
} else if (buttonIndex == 3) {
|
||||||
_liveReload = !_liveReload;
|
_liveReload = !_liveReload;
|
||||||
[self _pollAndReload];
|
[self _pollAndReload];
|
||||||
|
} else if (buttonIndex == 4) {
|
||||||
|
if (_bridge.profile) {
|
||||||
|
[_bridge stopProfiling];
|
||||||
|
} else {
|
||||||
|
[_bridge startProfiling];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@
|
||||||
|
|
||||||
_bridge = bridge;
|
_bridge = bridge;
|
||||||
_moduleName = moduleName;
|
_moduleName = moduleName;
|
||||||
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(bundleFinishedLoading)
|
selector:@selector(bundleFinishedLoading)
|
||||||
|
@ -105,7 +104,6 @@
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
[_touchHandler invalidate];
|
|
||||||
if (_contentView) {
|
if (_contentView) {
|
||||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||||
args:@[_contentView.reactTag]];
|
args:@[_contentView.reactTag]];
|
||||||
|
@ -148,7 +146,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
||||||
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
||||||
* the react tag is assigned every time we load new content.
|
* the react tag is assigned every time we load new content.
|
||||||
*/
|
*/
|
||||||
[_touchHandler invalidate];
|
|
||||||
[_contentView removeFromSuperview];
|
[_contentView removeFromSuperview];
|
||||||
_contentView = [[UIView alloc] initWithFrame:self.bounds];
|
_contentView = [[UIView alloc] initWithFrame:self.bounds];
|
||||||
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
|
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
|
||||||
|
|
|
@ -9,14 +9,12 @@
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#import "RCTInvalidating.h"
|
#import "RCTFrameUpdate.h"
|
||||||
|
|
||||||
@class RCTBridge;
|
@class RCTBridge;
|
||||||
|
|
||||||
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
|
@interface RCTTouchHandler : UIGestureRecognizer
|
||||||
|
|
||||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||||
- (void)startOrResetInteractionTiming;
|
|
||||||
- (NSDictionary *)endAndResetInteractionTiming;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -20,39 +20,6 @@
|
||||||
|
|
||||||
// TODO: this class behaves a lot like a module, and could be implemented as a
|
// TODO: this class behaves a lot like a module, and could be implemented as a
|
||||||
// module if we were to assume that modules and RootViews had a 1:1 relationship
|
// module if we were to assume that modules and RootViews had a 1:1 relationship
|
||||||
|
|
||||||
@interface RCTTouchEvent : NSObject
|
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) NSUInteger id;
|
|
||||||
@property (nonatomic, copy, readonly) NSString *eventName;
|
|
||||||
@property (nonatomic, copy, readonly) NSArray *touches;
|
|
||||||
@property (nonatomic, copy, readonly) NSArray *changedIndexes;
|
|
||||||
@property (nonatomic, assign, readonly) CFTimeInterval originatingTime;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@implementation RCTTouchEvent
|
|
||||||
|
|
||||||
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
|
|
||||||
{
|
|
||||||
RCTTouchEvent *touchEvent = [[self alloc] init];
|
|
||||||
touchEvent->_id = [self newTaskID];
|
|
||||||
touchEvent->_eventName = [eventName copy];
|
|
||||||
touchEvent->_touches = [touches copy];
|
|
||||||
touchEvent->_changedIndexes = [changedIndexes copy];
|
|
||||||
touchEvent->_originatingTime = originatingTime;
|
|
||||||
return touchEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (NSUInteger)newTaskID
|
|
||||||
{
|
|
||||||
static NSUInteger taskID = 0;
|
|
||||||
return ++taskID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation RCTTouchHandler
|
@implementation RCTTouchHandler
|
||||||
{
|
{
|
||||||
__weak RCTBridge *_bridge;
|
__weak RCTBridge *_bridge;
|
||||||
|
@ -69,7 +36,6 @@
|
||||||
|
|
||||||
BOOL _recordingInteractionTiming;
|
BOOL _recordingInteractionTiming;
|
||||||
CFTimeInterval _mostRecentEnqueueJS;
|
CFTimeInterval _mostRecentEnqueueJS;
|
||||||
CADisplayLink *_displayLink;
|
|
||||||
NSMutableArray *_pendingTouches;
|
NSMutableArray *_pendingTouches;
|
||||||
NSMutableArray *_bridgeInteractionTiming;
|
NSMutableArray *_bridgeInteractionTiming;
|
||||||
}
|
}
|
||||||
|
@ -86,12 +52,9 @@
|
||||||
_reactTouches = [[NSMutableArray alloc] init];
|
_reactTouches = [[NSMutableArray alloc] init];
|
||||||
_touchViews = [[NSMutableArray alloc] init];
|
_touchViews = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
|
|
||||||
_pendingTouches = [[NSMutableArray alloc] init];
|
_pendingTouches = [[NSMutableArray alloc] init];
|
||||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
|
||||||
|
|
||||||
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
|
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
|
||||||
// level components not build using RCT, will fail to recognize gestures.
|
// level components not build using RCT, will fail to recognize gestures.
|
||||||
self.cancelsTouchesInView = NO;
|
self.cancelsTouchesInView = NO;
|
||||||
|
@ -99,17 +62,6 @@
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isValid
|
|
||||||
{
|
|
||||||
return _displayLink != nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)invalidate
|
|
||||||
{
|
|
||||||
[_displayLink invalidate];
|
|
||||||
_displayLink = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||||
RCTTouchEventTypeStart,
|
RCTTouchEventTypeStart,
|
||||||
RCTTouchEventTypeMove,
|
RCTTouchEventTypeMove,
|
||||||
|
@ -216,7 +168,6 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
|
||||||
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
|
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
|
||||||
{
|
{
|
||||||
// Update touches
|
// Update touches
|
||||||
CFTimeInterval enqueueTime = CACurrentMediaTime();
|
|
||||||
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
|
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
NSInteger index = [_nativeTouches indexOfObject:touch];
|
NSInteger index = [_nativeTouches indexOfObject:touch];
|
||||||
|
@ -239,71 +190,8 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
|
||||||
[reactTouches addObject:[touch copy]];
|
[reactTouches addObject:[touch copy]];
|
||||||
}
|
}
|
||||||
|
|
||||||
RCTTouchEvent *touch = [RCTTouchEvent touchWithEventName:eventName
|
|
||||||
touches:reactTouches
|
|
||||||
changedIndexes:changedIndexes
|
|
||||||
originatingTime:originatingTime];
|
|
||||||
[_pendingTouches addObject:touch];
|
|
||||||
|
|
||||||
if (_recordingInteractionTiming) {
|
|
||||||
[_bridgeInteractionTiming addObject:@{
|
|
||||||
@"timeSeconds": @(touch.originatingTime),
|
|
||||||
@"operation": @"taskOriginated",
|
|
||||||
@"taskID": @(touch.id),
|
|
||||||
}];
|
|
||||||
[_bridgeInteractionTiming addObject:@{
|
|
||||||
@"timeSeconds": @(enqueueTime),
|
|
||||||
@"operation": @"taskEnqueuedPending",
|
|
||||||
@"taskID": @(touch.id),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_update:(CADisplayLink *)sender
|
|
||||||
{
|
|
||||||
// Dispatch touch event
|
|
||||||
NSUInteger pendingCount = _pendingTouches.count;
|
|
||||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
|
||||||
_mostRecentEnqueueJS = CACurrentMediaTime();
|
|
||||||
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
|
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
|
||||||
args:@[touch.eventName, touch.touches, touch.changedIndexes]];
|
args:@[eventName, reactTouches, changedIndexes]];
|
||||||
}
|
|
||||||
|
|
||||||
if (_recordingInteractionTiming) {
|
|
||||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
|
||||||
[_bridgeInteractionTiming addObject:@{
|
|
||||||
@"timeSeconds": @(sender.timestamp),
|
|
||||||
@"operation": @"frameAlignedDispatch",
|
|
||||||
@"taskID": @(touch.id),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
|
|
||||||
[_bridgeInteractionTiming addObject:@{
|
|
||||||
@"timeSeconds": @(sender.timestamp),
|
|
||||||
@"operation": @"mainThreadDisplayLink",
|
|
||||||
@"taskID": @([RCTTouchEvent newTaskID]),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[_pendingTouches removeAllObjects];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)startOrResetInteractionTiming
|
|
||||||
{
|
|
||||||
RCTAssertMainThread();
|
|
||||||
[_bridgeInteractionTiming removeAllObjects];
|
|
||||||
_recordingInteractionTiming = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary *)endAndResetInteractionTiming
|
|
||||||
{
|
|
||||||
RCTAssertMainThread();
|
|
||||||
_recordingInteractionTiming = NO;
|
|
||||||
NSArray *_prevInteractionTimingData = _bridgeInteractionTiming;
|
|
||||||
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
|
|
||||||
return @{ @"interactionTiming": _prevInteractionTimingData };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Gesture Recognizer Delegate Callbacks
|
#pragma mark - Gesture Recognizer Delegate Callbacks
|
||||||
|
|
|
@ -15,6 +15,15 @@
|
||||||
#import "RCTSparseArray.h"
|
#import "RCTSparseArray.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
|
@interface RCTBridge (Private)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow super fast, one time, timers to skip the queue and be directly executed
|
||||||
|
*/
|
||||||
|
- (void)_immediatelyCallTimer:(NSNumber *)timer;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface RCTTimer : NSObject
|
@interface RCTTimer : NSObject
|
||||||
|
|
||||||
@property (nonatomic, strong, readonly) NSDate *target;
|
@property (nonatomic, strong, readonly) NSDate *target;
|
||||||
|
@ -160,7 +169,7 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID
|
||||||
{
|
{
|
||||||
if (jsDuration == 0 && repeats == NO) {
|
if (jsDuration == 0 && repeats == NO) {
|
||||||
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
|
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
|
||||||
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[@[callbackID]]];
|
[_bridge _immediatelyCallTimer:callbackID];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -677,14 +677,12 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
||||||
|
|
||||||
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
||||||
|
|
||||||
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
|
||||||
|
|
||||||
// Figure out what to insert - merge temporary inserts and adds
|
// Figure out what to insert - merge temporary inserts and adds
|
||||||
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
||||||
for (NSInteger index = 0; index < temporarilyRemovedChildren.count; index++) {
|
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
||||||
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
||||||
}
|
}
|
||||||
for (NSInteger index = 0; index < addAtIndices.count; index++) {
|
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
|
||||||
id view = registry[addChildReactTags[index]];
|
id view = registry[addChildReactTags[index]];
|
||||||
if (view) {
|
if (view) {
|
||||||
destinationsToChildrenToAdd[addAtIndices[index]] = view;
|
destinationsToChildrenToAdd[addAtIndices[index]] = view;
|
||||||
|
@ -1420,41 +1418,6 @@ RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config
|
||||||
callback:callback];
|
callback:callback];
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(startOrResetInteractionTiming)
|
|
||||||
{
|
|
||||||
NSSet *rootViewTags = [_rootViewTags copy];
|
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
||||||
for (NSNumber *reactTag in rootViewTags) {
|
|
||||||
UIView *rootView = viewRegistry[reactTag];
|
|
||||||
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
|
|
||||||
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
|
|
||||||
[handler startOrResetInteractionTiming];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(endAndResetInteractionTiming:(RCTResponseSenderBlock)onSuccess
|
|
||||||
onError:(RCTResponseSenderBlock)onError)
|
|
||||||
{
|
|
||||||
NSSet *rootViewTags = [_rootViewTags copy];
|
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
||||||
NSMutableDictionary *timingData = [[NSMutableDictionary alloc] init];
|
|
||||||
for (NSNumber *reactTag in rootViewTags) {
|
|
||||||
UIView *rootView = viewRegistry[reactTag];
|
|
||||||
for (RCTTouchHandler *handler in rootView.gestureRecognizers) {
|
|
||||||
if ([handler isKindOfClass:[RCTTouchHandler class]]) {
|
|
||||||
[handler endAndResetInteractionTiming];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSuccess(@[timingData]);
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
static UIView *_jsResponder;
|
static UIView *_jsResponder;
|
||||||
|
|
||||||
+ (UIView *)JSResponder
|
+ (UIView *)JSResponder
|
||||||
|
|
|
@ -17,6 +17,8 @@ var Activity = require('../Activity');
|
||||||
var AssetServer = require('../AssetServer');
|
var AssetServer = require('../AssetServer');
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
var exec = require('child_process').exec;
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
module.exports = Server;
|
module.exports = Server;
|
||||||
|
|
||||||
|
@ -252,6 +254,42 @@ Server.prototype._processAssetsRequest = function(req, res) {
|
||||||
).done();
|
).done();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Server.prototype._processProfile = function(req, res) {
|
||||||
|
console.log('Dumping profile information...');
|
||||||
|
var dumpName = '/tmp/dump_' + Date.now() + '.json';
|
||||||
|
var prefix = process.env.TRACE_VIEWER_PATH || '';
|
||||||
|
var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName;
|
||||||
|
fs.writeFileSync(dumpName, req.rawBody);
|
||||||
|
exec(cmd, function (error) {
|
||||||
|
if (error) {
|
||||||
|
if (error.code === 127) {
|
||||||
|
console.error(
|
||||||
|
'\n** Failed executing `' + cmd + '` **\n\n' +
|
||||||
|
'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' +
|
||||||
|
'You can get it at:\n\n' +
|
||||||
|
' https://github.com/google/trace-viewer\n\n' +
|
||||||
|
'If it\'s not in your path, you can set a custom path with:\n\n' +
|
||||||
|
' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' +
|
||||||
|
'NOTE: Your profile data was kept at:\n\n' +
|
||||||
|
' ' + dumpName
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Unknown error', error);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
exec('rm ' + dumpName);
|
||||||
|
exec('open ' + dumpName.replace(/json$/, 'html'), function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
Server.prototype.processRequest = function(req, res, next) {
|
Server.prototype.processRequest = function(req, res, next) {
|
||||||
var urlObj = url.parse(req.url, true);
|
var urlObj = url.parse(req.url, true);
|
||||||
var pathname = urlObj.pathname;
|
var pathname = urlObj.pathname;
|
||||||
|
@ -270,6 +308,9 @@ Server.prototype.processRequest = function(req, res, next) {
|
||||||
} else if (pathname.match(/^\/assets\//)) {
|
} else if (pathname.match(/^\/assets\//)) {
|
||||||
this._processAssetsRequest(req, res);
|
this._processAssetsRequest(req, res);
|
||||||
return;
|
return;
|
||||||
|
} else if (pathname.match(/^\/profile\/?$/)) {
|
||||||
|
this._processProfile(req, res);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in New Issue