[ReactNative] Use a single DisplayLink held by the bridge
This commit is contained in:
parent
4f8b2825a0
commit
75e4e124fa
|
@ -10,6 +10,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
|
@ -122,4 +123,14 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
|||
*/
|
||||
- (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
|
||||
|
|
|
@ -677,6 +677,73 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
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
|
||||
{
|
||||
RCTSparseArray *_modulesByID;
|
||||
|
@ -685,6 +752,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
Class _executorClass;
|
||||
NSURL *_bundleURL;
|
||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
RCTDisplayLink *_displayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
BOOL _loading;
|
||||
}
|
||||
|
||||
|
@ -711,6 +780,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_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
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
|
@ -891,6 +962,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
[_displayLink invalidate];
|
||||
_frameUpdateObservers = nil;
|
||||
|
||||
// Invalidate modules
|
||||
for (id target in _modulesByID.allObjects) {
|
||||
if ([target respondsToSelector:@selector(invalidate)]) {
|
||||
|
@ -1075,6 +1149,26 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
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
|
||||
{
|
||||
if (!_loading) {
|
||||
|
|
|
@ -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
|
|
@ -10,8 +10,9 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating>
|
||||
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating, RCTFrameUpdateObserver>
|
||||
|
||||
@end
|
||||
|
|
|
@ -58,7 +58,6 @@
|
|||
@implementation RCTTiming
|
||||
{
|
||||
RCTSparseArray *_timers;
|
||||
id _updateTimer;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
@ -113,32 +112,21 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
|
|||
|
||||
- (void)stopTimers
|
||||
{
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
[_bridge removeFrameUpdateObserver:self];
|
||||
}
|
||||
|
||||
- (void)startTimers
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
|
||||
if (![self isValid] || _timers.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
|
||||
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];
|
||||
}
|
||||
[_bridge addFrameUpdateObserver:self];
|
||||
}
|
||||
|
||||
- (void)update
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
|
|
|
@ -154,6 +154,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -391,6 +392,7 @@
|
|||
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
|
||||
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
|
||||
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
|
||||
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
@ -9,16 +9,17 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTNavigator : UIView <RCTInvalidating>
|
||||
@interface RCTNavigator : UIView <RCTFrameUpdateObserver>
|
||||
|
||||
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
|
||||
@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
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import "RCTNavigator.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
|
@ -190,10 +191,6 @@ NSInteger kNeverProgressed = -10000;
|
|||
@end
|
||||
|
||||
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
|
||||
{
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
NSInteger _numberOfViewControllerMovesToIgnore;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
|
||||
|
||||
|
@ -251,7 +248,6 @@ NSInteger kNeverProgressed = -10000;
|
|||
*
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat mostRecentProgress;
|
||||
@property (nonatomic, readwrite, strong) CADisplayLink *displayLink;
|
||||
@property (nonatomic, readonly, strong) NSTimer *runTimer;
|
||||
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom;
|
||||
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo;
|
||||
|
@ -263,22 +259,17 @@ NSInteger kNeverProgressed = -10000;
|
|||
@end
|
||||
|
||||
@implementation RCTNavigator
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
NSInteger _numberOfViewControllerMovesToIgnore;
|
||||
}
|
||||
|
||||
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
- (id)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)];
|
||||
_bridge = bridge;
|
||||
_mostRecentProgress = kNeverProgressed;
|
||||
_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.
|
||||
_previousViews = @[];
|
||||
_currentViews = [[NSMutableArray alloc] initWithCapacity:0];
|
||||
|
@ -295,7 +286,7 @@ NSInteger kNeverProgressed = -10000;
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void)reportNavigationProgress:(CADisplayLink *)sender
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
if (_currentlyTransitioningFrom != _currentlyTransitioningTo) {
|
||||
UIView *topView = _dummyView;
|
||||
|
@ -307,7 +298,7 @@ NSInteger kNeverProgressed = -10000;
|
|||
return;
|
||||
}
|
||||
_mostRecentProgress = nextProgress;
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
|
||||
@"fromIndex": @(_currentlyTransitioningFrom),
|
||||
@"toIndex": @(_currentlyTransitioningTo),
|
||||
@"progress": @(nextProgress),
|
||||
|
@ -350,16 +341,14 @@ NSInteger kNeverProgressed = -10000;
|
|||
_dummyView.frame = (CGRect){{destination}};
|
||||
_currentlyTransitioningFrom = indexOfFrom;
|
||||
_currentlyTransitioningTo = indexOfTo;
|
||||
if (indexOfFrom != indexOfTo) {
|
||||
_displayLink.paused = NO;
|
||||
}
|
||||
[_bridge addFrameUpdateObserver:self];
|
||||
}
|
||||
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
[weakSelf freeLock];
|
||||
_currentlyTransitioningFrom = 0;
|
||||
_currentlyTransitioningTo = 0;
|
||||
_dummyView.frame = CGRectZero;
|
||||
_displayLink.paused = YES;
|
||||
[_bridge removeFrameUpdateObserver:self];
|
||||
// Reset the parallel position tracker
|
||||
}];
|
||||
}
|
||||
|
@ -400,19 +389,6 @@ NSInteger kNeverProgressed = -10000;
|
|||
return _currentViews;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Prevent displayLink from retaining the navigator indefinitely
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
_runTimer = nil;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
@ -430,7 +406,7 @@ NSInteger kNeverProgressed = -10000;
|
|||
|
||||
- (void)handleTopOfStackChanged
|
||||
{
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
|
||||
@"target":self.reactTag,
|
||||
@"stackLength":@(_navigationController.viewControllers.count)
|
||||
}];
|
||||
|
@ -438,7 +414,7 @@ NSInteger kNeverProgressed = -10000;
|
|||
|
||||
- (void)dispatchFakeScrollEvent
|
||||
{
|
||||
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
|
||||
[_bridge.eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
|
||||
reactTag:self.reactTag
|
||||
scrollView:nil
|
||||
userData:nil];
|
||||
|
@ -511,7 +487,7 @@ NSInteger kNeverProgressed = -10000;
|
|||
if (jsGettingAhead) {
|
||||
if (reactPushOne) {
|
||||
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;
|
||||
_numberOfViewControllerMovesToIgnore = 1;
|
||||
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];
|
||||
|
|
|
@ -21,7 +21,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
return [[RCTNavigator alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
|
||||
|
|
Loading…
Reference in New Issue