[ReactNative] Use a single DisplayLink held by the bridge

This commit is contained in:
Tadeu Zagallo 2015-04-15 07:07:19 -07:00
parent 4f8b2825a0
commit 75e4e124fa
9 changed files with 176 additions and 59 deletions

View File

@ -10,6 +10,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h" #import "RCTJavaScriptExecutor.h"
@ -122,4 +123,14 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
*/ */
- (void)reload; - (void)reload;
/**
* Add a new observer that will be called on every screen refresh
*/
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
/**
* Stop receiving screen refresh updates for the given observer
*/
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
@end @end

View File

@ -677,6 +677,73 @@ static NSDictionary *RCTLocalModulesConfig()
return localModules; return localModules;
} }
@interface RCTDisplayLink : NSObject <RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end
@interface RCTBridge (RCTDisplayLink)
- (void)_update:(CADisplayLink *)displayLink;
@end
@implementation RCTDisplayLink
{
__weak RCTBridge *_bridge;
CADisplayLink *_displayLink;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
if (self.isValid) {
[_displayLink invalidate];
_displayLink = nil;
}
}
- (void)_update:(CADisplayLink *)displayLink
{
[_bridge _update:displayLink];
}
@end
@interface RCTFrameUpdate (Private)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
@end
@implementation RCTFrameUpdate
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
{
if ((self = [super init])) {
_timestamp = displayLink.timestamp;
_deltaTime = displayLink.duration;
}
return self;
}
@end
@implementation RCTBridge @implementation RCTBridge
{ {
RCTSparseArray *_modulesByID; RCTSparseArray *_modulesByID;
@ -685,6 +752,8 @@ static NSDictionary *RCTLocalModulesConfig()
Class _executorClass; Class _executorClass;
NSURL *_bundleURL; NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider; RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
NSMutableSet *_frameUpdateObservers;
BOOL _loading; BOOL _loading;
} }
@ -711,6 +780,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_latestJSExecutor = _javaScriptExecutor; _latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_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];
_frameUpdateObservers = [[NSMutableSet alloc] init];
// Register passed-in module instances // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
@ -891,6 +962,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_javaScriptExecutor invalidate]; [_javaScriptExecutor invalidate];
_javaScriptExecutor = nil; _javaScriptExecutor = nil;
[_displayLink invalidate];
_frameUpdateObservers = nil;
// Invalidate modules // Invalidate modules
for (id target in _modulesByID.allObjects) { for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) { if ([target respondsToSelector:@selector(invalidate)]) {
@ -1075,6 +1149,26 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return YES; return YES;
} }
- (void)_update:(CADisplayLink *)displayLink
{
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
[observer didUpdateFrame:frameUpdate];
}
}
}
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers addObject:observer];
}
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers removeObject:observer];
}
- (void)reload - (void)reload
{ {
if (!_loading) { if (!_loading) {

View File

@ -0,0 +1,44 @@
/**
* 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.
*/
/**
* Interface containing the information about the last screen refresh.
*/
@interface RCTFrameUpdate : NSObject
/**
* Timestamp for the actual screen refresh
*/
@property (nonatomic, readonly) NSTimeInterval timestamp;
/**
* Time since the last frame update ( >= 16.6ms )
*/
@property (nonatomic, readonly) NSTimeInterval deltaTime;
@end
/**
* Protocol that must be implemented for subscribing to display refreshes (DisplayLink updates)
*/
@protocol RCTFrameUpdateObserver <NSObject>
/**
* Method called on every screen refresh (if paused != YES)
*/
- (void)didUpdateFrame:(RCTFrameUpdate *)update;
@optional
/**
* Synthesize and set to true to pause the calls to -[didUpdateFrame:]
*/
@property (nonatomic, assign, getter=isPaused) BOOL paused;
@end

View File

@ -10,8 +10,9 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating> @interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating, RCTFrameUpdateObserver>
@end @end

View File

@ -58,7 +58,6 @@
@implementation RCTTiming @implementation RCTTiming
{ {
RCTSparseArray *_timers; RCTSparseArray *_timers;
id _updateTimer;
} }
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@ -113,32 +112,21 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (void)stopTimers - (void)stopTimers
{ {
[_updateTimer invalidate]; [_bridge removeFrameUpdateObserver:self];
_updateTimer = nil;
} }
- (void)startTimers - (void)startTimers
{ {
RCTAssertMainThread(); RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil || _timers.count == 0) { if (![self isValid] || _timers.count == 0) {
return; return;
} }
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; [_bridge addFrameUpdateObserver:self];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
target:self
selector:@selector(update)
userInfo:nil
repeats:YES];
}
} }
- (void)update - (void)didUpdateFrame:(RCTFrameUpdate *)update
{ {
RCTAssertMainThread(); RCTAssertMainThread();

View File

@ -154,6 +154,7 @@
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; }; 13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; }; 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; }; 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = "<group>"; };
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; }; 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; }; 14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; }; 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
@ -391,6 +392,7 @@
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */, 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */, 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */, 83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
); );
path = Base; path = Base;
sourceTree = "<group>"; sourceTree = "<group>";

View File

@ -9,16 +9,17 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "RCTFrameUpdate.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
@class RCTEventDispatcher; @class RCTBridge;
@interface RCTNavigator : UIView <RCTInvalidating> @interface RCTNavigator : UIView <RCTFrameUpdateObserver>
@property (nonatomic, strong) UIView *reactNavSuperviewLink; @property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack; @property (nonatomic, assign) NSInteger requestedTopOfStack;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
/** /**
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until * Schedules a JavaScript navigation and prevents `UIKit` from navigating until

View File

@ -10,6 +10,7 @@
#import "RCTNavigator.h" #import "RCTNavigator.h"
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
@ -190,10 +191,6 @@ NSInteger kNeverProgressed = -10000;
@end @end
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate> @interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
{
RCTEventDispatcher *_eventDispatcher;
NSInteger _numberOfViewControllerMovesToIgnore;
}
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack; @property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
@ -251,7 +248,6 @@ NSInteger kNeverProgressed = -10000;
* *
*/ */
@property (nonatomic, readonly, assign) CGFloat mostRecentProgress; @property (nonatomic, readonly, assign) CGFloat mostRecentProgress;
@property (nonatomic, readwrite, strong) CADisplayLink *displayLink;
@property (nonatomic, readonly, strong) NSTimer *runTimer; @property (nonatomic, readonly, strong) NSTimer *runTimer;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom; @property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo; @property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo;
@ -263,22 +259,17 @@ NSInteger kNeverProgressed = -10000;
@end @end
@implementation RCTNavigator @implementation RCTNavigator
{
__weak RCTBridge *_bridge;
NSInteger _numberOfViewControllerMovesToIgnore;
}
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (id)initWithBridge:(RCTBridge *)bridge
{ {
if ((self = [super initWithFrame:CGRectZero])) { if ((self = [super initWithFrame:CGRectZero])) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)]; _bridge = bridge;
_mostRecentProgress = kNeverProgressed; _mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero]; _dummyView = [[UIView alloc] initWithFrame:CGRectZero];
if (_displayLink) {
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
_displayLink.paused = YES;
} else {
// It's okay to leak this on a build bot.
RCTLogWarn(@"Failed to create a display link (probably on automated build system) - using an NSTimer for AppEngine instead.");
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(reportNavigationProgress:) userInfo:nil repeats:YES];
}
_eventDispatcher = eventDispatcher;
_previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push. _previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push.
_previousViews = @[]; _previousViews = @[];
_currentViews = [[NSMutableArray alloc] initWithCapacity:0]; _currentViews = [[NSMutableArray alloc] initWithCapacity:0];
@ -295,7 +286,7 @@ NSInteger kNeverProgressed = -10000;
return self; return self;
} }
- (void)reportNavigationProgress:(CADisplayLink *)sender - (void)didUpdateFrame:(RCTFrameUpdate *)update
{ {
if (_currentlyTransitioningFrom != _currentlyTransitioningTo) { if (_currentlyTransitioningFrom != _currentlyTransitioningTo) {
UIView *topView = _dummyView; UIView *topView = _dummyView;
@ -307,7 +298,7 @@ NSInteger kNeverProgressed = -10000;
return; return;
} }
_mostRecentProgress = nextProgress; _mostRecentProgress = nextProgress;
[_eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{ [_bridge.eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
@"fromIndex": @(_currentlyTransitioningFrom), @"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo), @"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress), @"progress": @(nextProgress),
@ -350,16 +341,14 @@ NSInteger kNeverProgressed = -10000;
_dummyView.frame = (CGRect){{destination}}; _dummyView.frame = (CGRect){{destination}};
_currentlyTransitioningFrom = indexOfFrom; _currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo; _currentlyTransitioningTo = indexOfTo;
if (indexOfFrom != indexOfTo) { [_bridge addFrameUpdateObserver:self];
_displayLink.paused = NO;
}
} }
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[weakSelf freeLock]; [weakSelf freeLock];
_currentlyTransitioningFrom = 0; _currentlyTransitioningFrom = 0;
_currentlyTransitioningTo = 0; _currentlyTransitioningTo = 0;
_dummyView.frame = CGRectZero; _dummyView.frame = CGRectZero;
_displayLink.paused = YES; [_bridge removeFrameUpdateObserver:self];
// Reset the parallel position tracker // Reset the parallel position tracker
}]; }];
} }
@ -400,19 +389,6 @@ NSInteger kNeverProgressed = -10000;
return _currentViews; return _currentViews;
} }
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
// Prevent displayLink from retaining the navigator indefinitely
[_displayLink invalidate];
_displayLink = nil;
_runTimer = nil;
}
- (void)layoutSubviews - (void)layoutSubviews
{ {
[super layoutSubviews]; [super layoutSubviews];
@ -430,7 +406,7 @@ NSInteger kNeverProgressed = -10000;
- (void)handleTopOfStackChanged - (void)handleTopOfStackChanged
{ {
[_eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{ [_bridge.eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
@"target":self.reactTag, @"target":self.reactTag,
@"stackLength":@(_navigationController.viewControllers.count) @"stackLength":@(_navigationController.viewControllers.count)
}]; }];
@ -438,7 +414,7 @@ NSInteger kNeverProgressed = -10000;
- (void)dispatchFakeScrollEvent - (void)dispatchFakeScrollEvent
{ {
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove [_bridge.eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
reactTag:self.reactTag reactTag:self.reactTag
scrollView:nil scrollView:nil
userData:nil]; userData:nil];
@ -511,7 +487,7 @@ NSInteger kNeverProgressed = -10000;
if (jsGettingAhead) { if (jsGettingAhead) {
if (reactPushOne) { if (reactPushOne) {
UIView *lastView = [_currentViews lastObject]; UIView *lastView = [_currentViews lastObject];
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_eventDispatcher]; RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_bridge.eventDispatcher];
vc.navigationListener = self; vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1; _numberOfViewControllerMovesToIgnore = 1;
[_navigationController pushViewController:vc animated:(currentReactCount > 1)]; [_navigationController pushViewController:vc animated:(currentReactCount > 1)];

View File

@ -21,7 +21,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view - (UIView *)view
{ {
return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; return [[RCTNavigator alloc] initWithBridge:self.bridge];
} }
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger) RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)