Skip to content

Commit 75e4e12

Browse files
committed
[ReactNative] Use a single DisplayLink held by the bridge
1 parent 4f8b282 commit 75e4e12

File tree

9 files changed

+176
-59
lines changed

9 files changed

+176
-59
lines changed

React/Base/RCTBridge.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <UIKit/UIKit.h>
1111

1212
#import "RCTBridgeModule.h"
13+
#import "RCTFrameUpdate.h"
1314
#import "RCTInvalidating.h"
1415
#import "RCTJavaScriptExecutor.h"
1516

@@ -122,4 +123,14 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
122123
*/
123124
- (void)reload;
124125

126+
/**
127+
* Add a new observer that will be called on every screen refresh
128+
*/
129+
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
130+
131+
/**
132+
* Stop receiving screen refresh updates for the given observer
133+
*/
134+
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
135+
125136
@end

React/Base/RCTBridge.m

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,73 @@ - (NSString *)description
677677
return localModules;
678678
}
679679

680+
@interface RCTDisplayLink : NSObject <RCTInvalidating>
681+
682+
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
683+
684+
@end
685+
686+
@interface RCTBridge (RCTDisplayLink)
687+
688+
- (void)_update:(CADisplayLink *)displayLink;
689+
690+
@end
691+
692+
@implementation RCTDisplayLink
693+
{
694+
__weak RCTBridge *_bridge;
695+
CADisplayLink *_displayLink;
696+
}
697+
698+
- (instancetype)initWithBridge:(RCTBridge *)bridge
699+
{
700+
if ((self = [super init])) {
701+
_bridge = bridge;
702+
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
703+
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
704+
}
705+
return self;
706+
}
707+
708+
- (BOOL)isValid
709+
{
710+
return _displayLink != nil;
711+
}
712+
713+
- (void)invalidate
714+
{
715+
if (self.isValid) {
716+
[_displayLink invalidate];
717+
_displayLink = nil;
718+
}
719+
}
720+
721+
- (void)_update:(CADisplayLink *)displayLink
722+
{
723+
[_bridge _update:displayLink];
724+
}
725+
726+
@end
727+
728+
@interface RCTFrameUpdate (Private)
729+
730+
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
731+
732+
@end
733+
734+
@implementation RCTFrameUpdate
735+
736+
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
737+
{
738+
if ((self = [super init])) {
739+
_timestamp = displayLink.timestamp;
740+
_deltaTime = displayLink.duration;
741+
}
742+
return self;
743+
}
744+
745+
@end
746+
680747
@implementation RCTBridge
681748
{
682749
RCTSparseArray *_modulesByID;
@@ -685,6 +752,8 @@ @implementation RCTBridge
685752
Class _executorClass;
686753
NSURL *_bundleURL;
687754
RCTBridgeModuleProviderBlock _moduleProvider;
755+
RCTDisplayLink *_displayLink;
756+
NSMutableSet *_frameUpdateObservers;
688757
BOOL _loading;
689758
}
690759

@@ -711,6 +780,8 @@ - (void)setUp
711780
_latestJSExecutor = _javaScriptExecutor;
712781
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
713782
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
783+
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
784+
_frameUpdateObservers = [[NSMutableSet alloc] init];
714785

715786
// Register passed-in module instances
716787
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@@ -891,6 +962,9 @@ - (void)invalidate
891962
[_javaScriptExecutor invalidate];
892963
_javaScriptExecutor = nil;
893964

965+
[_displayLink invalidate];
966+
_frameUpdateObservers = nil;
967+
894968
// Invalidate modules
895969
for (id target in _modulesByID.allObjects) {
896970
if ([target respondsToSelector:@selector(invalidate)]) {
@@ -1075,6 +1149,26 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
10751149
return YES;
10761150
}
10771151

1152+
- (void)_update:(CADisplayLink *)displayLink
1153+
{
1154+
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
1155+
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
1156+
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
1157+
[observer didUpdateFrame:frameUpdate];
1158+
}
1159+
}
1160+
}
1161+
1162+
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
1163+
{
1164+
[_frameUpdateObservers addObject:observer];
1165+
}
1166+
1167+
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
1168+
{
1169+
[_frameUpdateObservers removeObject:observer];
1170+
}
1171+
10781172
- (void)reload
10791173
{
10801174
if (!_loading) {

React/Base/RCTFrameUpdate.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
/**
11+
* Interface containing the information about the last screen refresh.
12+
*/
13+
@interface RCTFrameUpdate : NSObject
14+
15+
/**
16+
* Timestamp for the actual screen refresh
17+
*/
18+
@property (nonatomic, readonly) NSTimeInterval timestamp;
19+
20+
/**
21+
* Time since the last frame update ( >= 16.6ms )
22+
*/
23+
@property (nonatomic, readonly) NSTimeInterval deltaTime;
24+
25+
@end
26+
27+
/**
28+
* Protocol that must be implemented for subscribing to display refreshes (DisplayLink updates)
29+
*/
30+
@protocol RCTFrameUpdateObserver <NSObject>
31+
32+
/**
33+
* Method called on every screen refresh (if paused != YES)
34+
*/
35+
- (void)didUpdateFrame:(RCTFrameUpdate *)update;
36+
37+
@optional
38+
39+
/**
40+
* Synthesize and set to true to pause the calls to -[didUpdateFrame:]
41+
*/
42+
@property (nonatomic, assign, getter=isPaused) BOOL paused;
43+
44+
@end

React/Modules/RCTTiming.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
#import <Foundation/Foundation.h>
1111

1212
#import "RCTBridgeModule.h"
13+
#import "RCTFrameUpdate.h"
1314
#import "RCTInvalidating.h"
1415

15-
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating>
16+
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating, RCTFrameUpdateObserver>
1617

1718
@end

React/Modules/RCTTiming.m

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ - (BOOL)updateFoundNeedsJSUpdate
5858
@implementation RCTTiming
5959
{
6060
RCTSparseArray *_timers;
61-
id _updateTimer;
6261
}
6362

6463
@synthesize bridge = _bridge;
@@ -113,32 +112,21 @@ - (void)invalidate
113112

114113
- (void)stopTimers
115114
{
116-
[_updateTimer invalidate];
117-
_updateTimer = nil;
115+
[_bridge removeFrameUpdateObserver:self];
118116
}
119117

120118
- (void)startTimers
121119
{
122120
RCTAssertMainThread();
123121

124-
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
122+
if (![self isValid] || _timers.count == 0) {
125123
return;
126124
}
127125

128-
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
129-
if (_updateTimer) {
130-
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
131-
} else {
132-
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
133-
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
134-
target:self
135-
selector:@selector(update)
136-
userInfo:nil
137-
repeats:YES];
138-
}
126+
[_bridge addFrameUpdateObserver:self];
139127
}
140128

141-
- (void)update
129+
- (void)didUpdateFrame:(RCTFrameUpdate *)update
142130
{
143131
RCTAssertMainThread();
144132

React/React.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
155155
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
156156
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
157+
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = "<group>"; };
157158
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
158159
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
159160
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
@@ -391,6 +392,7 @@
391392
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
392393
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
393394
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
395+
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
394396
);
395397
path = Base;
396398
sourceTree = "<group>";

React/Views/RCTNavigator.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@
99

1010
#import <UIKit/UIKit.h>
1111

12+
#import "RCTFrameUpdate.h"
1213
#import "RCTInvalidating.h"
1314

14-
@class RCTEventDispatcher;
15+
@class RCTBridge;
1516

16-
@interface RCTNavigator : UIView <RCTInvalidating>
17+
@interface RCTNavigator : UIView <RCTFrameUpdateObserver>
1718

1819
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
1920
@property (nonatomic, assign) NSInteger requestedTopOfStack;
2021

21-
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
22+
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
2223

2324
/**
2425
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until

0 commit comments

Comments
 (0)