|
50 | 50 | static NSTimeInterval RCTProfileStartTime; |
51 | 51 | static NSUInteger RCTProfileEventID = 0; |
52 | 52 | static CADisplayLink *RCTProfileDisplayLink; |
| 53 | +static __weak RCTBridge *_RCTProfilingBridge; |
| 54 | +static UIWindow *RCTProfileControlsWindow; |
53 | 55 |
|
54 | 56 | #pragma mark - Macros |
55 | 57 |
|
@@ -97,6 +99,11 @@ void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb) |
97 | 99 |
|
98 | 100 | #pragma mark - Private Helpers |
99 | 101 |
|
| 102 | +static RCTBridge *RCTProfilingBridge(void) |
| 103 | +{ |
| 104 | + return _RCTProfilingBridge ?: [RCTBridge currentBridge]; |
| 105 | +} |
| 106 | + |
100 | 107 | static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) |
101 | 108 | { |
102 | 109 | return @((timestamp - RCTProfileStartTime) * 1e6); |
@@ -288,6 +295,8 @@ static void RCTProfileHookInstance(id instance) |
288 | 295 |
|
289 | 296 | void RCTProfileHookModules(RCTBridge *bridge) |
290 | 297 | { |
| 298 | + _RCTProfilingBridge = bridge; |
| 299 | + |
291 | 300 | #pragma clang diagnostic push |
292 | 301 | #pragma clang diagnostic ignored "-Wtautological-pointer-compare" |
293 | 302 | if (RCTProfileTrampoline == NULL) { |
@@ -324,6 +333,8 @@ static void RCTProfileUnhookInstance(id instance) |
324 | 333 |
|
325 | 334 | void RCTProfileUnhookModules(RCTBridge *bridge) |
326 | 335 | { |
| 336 | + _RCTProfilingBridge = nil; |
| 337 | + |
327 | 338 | dispatch_group_enter(RCTProfileGetUnhookGroup()); |
328 | 339 |
|
329 | 340 | for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { |
@@ -351,6 +362,54 @@ + (void)vsync:(CADisplayLink *)displayLink |
351 | 362 | RCTProfileImmediateEvent(0, @"VSYNC", displayLink.timestamp, 'g'); |
352 | 363 | } |
353 | 364 |
|
| 365 | ++ (void)reload |
| 366 | +{ |
| 367 | + [RCTProfilingBridge() reload]; |
| 368 | +} |
| 369 | + |
| 370 | ++ (void)toggle:(UIButton *)target |
| 371 | +{ |
| 372 | + BOOL isProfiling = RCTProfileIsProfiling(); |
| 373 | + |
| 374 | + // Start and Stop are switched here, since we're going to toggle isProfiling |
| 375 | + [target setTitle:isProfiling ? @"Start" : @"Stop" |
| 376 | + forState:UIControlStateNormal]; |
| 377 | + |
| 378 | + if (isProfiling) { |
| 379 | + RCTProfileEnd(RCTProfilingBridge(), ^(NSString *result) { |
| 380 | + NSString *outFile = [NSTemporaryDirectory() stringByAppendingString:@"tmp_trace.json"]; |
| 381 | + [result writeToFile:outFile |
| 382 | + atomically:YES |
| 383 | + encoding:NSUTF8StringEncoding |
| 384 | + error:nil]; |
| 385 | + UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:outFile]] |
| 386 | + applicationActivities:nil]; |
| 387 | + activityViewController.completionHandler = ^(__unused NSString *activityType, __unused BOOL completed) { |
| 388 | + RCTProfileControlsWindow.hidden = NO; |
| 389 | + }; |
| 390 | + RCTProfileControlsWindow.hidden = YES; |
| 391 | + dispatch_async(dispatch_get_main_queue(), ^{ |
| 392 | + [[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:activityViewController |
| 393 | + animated:YES |
| 394 | + completion:nil]; |
| 395 | + }); |
| 396 | + }); |
| 397 | + } else { |
| 398 | + RCTProfileInit(RCTProfilingBridge()); |
| 399 | + } |
| 400 | +} |
| 401 | + |
| 402 | ++ (void)drag:(UIPanGestureRecognizer *)gestureRecognizer |
| 403 | +{ |
| 404 | + CGPoint translation = [gestureRecognizer translationInView:RCTProfileControlsWindow]; |
| 405 | + RCTProfileControlsWindow.center = CGPointMake( |
| 406 | + RCTProfileControlsWindow.center.x + translation.x, |
| 407 | + RCTProfileControlsWindow.center.y + translation.y |
| 408 | + ); |
| 409 | + [gestureRecognizer setTranslation:CGPointMake(0, 0) |
| 410 | + inView:RCTProfileControlsWindow]; |
| 411 | +} |
| 412 | + |
354 | 413 | @end |
355 | 414 |
|
356 | 415 | #pragma mark - Public Functions |
@@ -713,4 +772,44 @@ void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data) |
713 | 772 | [task resume]; |
714 | 773 | } |
715 | 774 |
|
| 775 | +void RCTProfileShowControls(void) |
| 776 | +{ |
| 777 | + static const CGFloat height = 30; |
| 778 | + static const CGFloat width = 60; |
| 779 | + |
| 780 | + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(20, 80, width * 2, height)]; |
| 781 | + window.windowLevel = UIWindowLevelAlert + 1000; |
| 782 | + window.hidden = NO; |
| 783 | + window.backgroundColor = [UIColor lightGrayColor]; |
| 784 | + window.layer.borderColor = [UIColor grayColor].CGColor; |
| 785 | + window.layer.borderWidth = 1; |
| 786 | + window.alpha = 0.8; |
| 787 | + |
| 788 | + UIButton *startOrStop = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, height)]; |
| 789 | + [startOrStop setTitle:RCTProfileIsProfiling() ? @"Stop" : @"Start" |
| 790 | + forState:UIControlStateNormal]; |
| 791 | + [startOrStop addTarget:[RCTProfile class] action:@selector(toggle:) forControlEvents:UIControlEventTouchUpInside]; |
| 792 | + startOrStop.titleLabel.font = [UIFont systemFontOfSize:12]; |
| 793 | + |
| 794 | + UIButton *reload = [[UIButton alloc] initWithFrame:CGRectMake(width, 0, width, height)]; |
| 795 | + [reload setTitle:@"Reload" forState:UIControlStateNormal]; |
| 796 | + [reload addTarget:[RCTProfile class] action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; |
| 797 | + reload.titleLabel.font = [UIFont systemFontOfSize:12]; |
| 798 | + |
| 799 | + [window addSubview:startOrStop]; |
| 800 | + [window addSubview:reload]; |
| 801 | + |
| 802 | + UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:[RCTProfile class] |
| 803 | + action:@selector(drag:)]; |
| 804 | + [window addGestureRecognizer:gestureRecognizer]; |
| 805 | + |
| 806 | + RCTProfileControlsWindow = window; |
| 807 | +} |
| 808 | + |
| 809 | +void RCTProfileHideControls(void) |
| 810 | +{ |
| 811 | + RCTProfileControlsWindow.hidden = YES; |
| 812 | + RCTProfileControlsWindow = nil; |
| 813 | +} |
| 814 | + |
716 | 815 | #endif |
0 commit comments