Skip to content

Commit

Permalink
DCDDisplayLink
Browse files Browse the repository at this point in the history
  • Loading branch information
TheSisb authored and Ivan K committed Sep 20, 2017
1 parent b61fe7f commit 62a487a
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 8 deletions.
61 changes: 61 additions & 0 deletions React/Base/DCDDisplayLink.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// RCTDisplayLink.h
// React
//
// Created by Stanislav Vishnevskiy on 6/8/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import <QuartzCore/CABase.h>
#import <Foundation/NSObject.h>

@class NSString, NSRunLoop;

@interface DCDDisplayLink : NSObject

/* Create a new display link object for the main display. It will
* invoke the method called 'sel' on 'target', the method has the
* signature '(void)selector:(CADisplayLink *)sender'. */

+ (DCDDisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;

/* Adds the receiver to the given run-loop and mode. Unless paused, it
* will fire every vsync until removed. Each object may only be added
* to a single run-loop, but it may be added in multiple modes at once.
* While added to a run-loop it will implicitly be retained. */

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

/* Removes the receiver from the given mode of the runloop. This will
* implicitly release it when removed from the last mode it has been
* registered for. */

- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

/* Removes the object from all runloop modes (releasing the receiver if
* it has been implicitly retained) and releases the 'target' object. */

- (void)invalidate;

/* The current time, and duration of the display frame associated with
* the most recent target invocation. Time is represented using the
* normal Core Animation conventions, i.e. Mach host time converted to
* seconds. */

@property(readonly, nonatomic) CFTimeInterval timestamp;
@property(readonly, nonatomic) CFTimeInterval duration;

/* When true the object is prevented from firing. Initial state is
* false. */

@property(getter=isPaused, nonatomic) BOOL paused;

/* Defines how many display frames must pass between each time the
* display link fires. Default value is one, which means the display
* link will fire for every display frame. Setting the interval to two
* will cause the display link to fire every other display frame, and
* so on. The behavior when using values less than one is undefined. */

@property(nonatomic) NSInteger frameInterval;

@end
159 changes: 159 additions & 0 deletions React/Base/DCDDisplayLink.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//
// RCTDisplayLink.m
// React
//
// Created by Stanislav Vishnevskiy on 6/8/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import "DCDDisplayLink.h"
#import <UIKit/UIKit.h>

@interface DCDDisplayLink ()

@property(nonatomic) NSRunLoop *runloop;
@property(nonatomic) NSString *mode;
@property(nonatomic) id target;
@property(nonatomic) SEL selector;
@property(nonatomic) NSTimer *timer;
@property(nonatomic) CADisplayLink *displayLink;

// CADisplayLink is not thread safe.
// Add a flag to avoid the crash of removing invalidated CADisplayLink from the run loop.
@property(nonatomic) BOOL resourcesLoaded;

@end

@implementation DCDDisplayLink

+ (DCDDisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel {
return [[self alloc] initWithTarget:target selector:sel];
}

- (instancetype)initWithTarget:(id)target selector:(SEL)sel {
if (self = [super init]) {
_target = target;
_selector = sel;
_displayLink = [CADisplayLink displayLinkWithTarget:target selector:sel];
_resourcesLoaded = YES;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(switchToTimer)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(switchToDisplayLink)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)switchToDisplayLink {
if (_timer) {
[_timer invalidate];
_timer = nil;
[self setPaused:_paused];
if (_runloop) {
[_displayLink addToRunLoop:_runloop forMode:_mode];
}
}
}

- (void)switchToTimer {
if (!_timer) {
[self maybeResetTimer];
[self setPaused:_paused];
if (_runloop && _resourcesLoaded) {
[_displayLink removeFromRunLoop:_runloop forMode:_mode];
[_runloop addTimer:_timer forMode:_mode];
}
}
}

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode {
_runloop = runloop;
_mode = mode;
if (_timer) {
[self maybeResetTimer];
[runloop addTimer:_timer forMode:mode];
}
else {
[_displayLink addToRunLoop:runloop forMode:mode];
}
}

- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode {
_runloop = nil;
_mode = nil;
if (_timer) {
[_timer invalidate];
}
else {
[_displayLink removeFromRunLoop:runloop forMode:mode];
}
}

- (void)invalidate {
_resourcesLoaded = NO;
if (_timer) {
[_timer invalidate];
}
else {
[_displayLink invalidate];
}
}

- (void)setPaused:(BOOL)paused {
_paused = paused;
if (_timer) {
if (paused) {
[_timer invalidate];
}
else {
[self maybeResetTimer];
if (_runloop) {
[_runloop addTimer:_timer forMode:_mode];
}
}
}
else {
_displayLink.paused = paused;
}
}

- (CFTimeInterval)timestamp {
if (_timer) {
// TODO: Does React Native actually need this?
return 0;
}
return _displayLink.timestamp;
}

- (CFTimeInterval)duration {
if (_timer) {
// TODO: Does React Native actually need this?
return 0;
}
return _displayLink.duration;
}

- (void)maybeResetTimer {
if (!_timer || ![_timer isValid]) {
_timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerLoop) userInfo:nil repeats:YES];
}
}

- (void)timerLoop {
if (_target) {
IMP imp = [_target methodForSelector:_selector];
void (*func)(id, SEL, DCDDisplayLink *) = (void *)imp;
func(_target, _selector, self);
}
}

@end
8 changes: 4 additions & 4 deletions React/Base/RCTDisplayLink.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#import "RCTDisplayLink.h"

#import <Foundation/Foundation.h>
#import <QuartzCore/CADisplayLink.h>
#import "DCDDisplayLink.h"

#import "RCTAssert.h"
#import "RCTBridgeModule.h"
Expand All @@ -24,7 +24,7 @@

@implementation RCTDisplayLink
{
CADisplayLink *_jsDisplayLink;
DCDDisplayLink *_jsDisplayLink;
NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
NSRunLoop *_runLoop;
}
Expand All @@ -33,7 +33,7 @@ - (instancetype)init
{
if ((self = [super init])) {
_frameUpdateObservers = [NSMutableSet new];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
_jsDisplayLink = [DCDDisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
}

return self;
Expand Down Expand Up @@ -104,7 +104,7 @@ - (void)dispatchBlock:(dispatch_block_t)block
}
}

- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
- (void)_jsThreadUpdate:(DCDDisplayLink *)displayLink
{
RCTAssertRunLoop();

Expand Down
4 changes: 2 additions & 2 deletions React/Base/RCTFrameUpdate.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

#import <Foundation/Foundation.h>

@class CADisplayLink;
@class DCDDisplayLink;

/**
* Interface containing the information about the last screen refresh.
Expand All @@ -26,7 +26,7 @@
*/
@property (nonatomic, readonly) NSTimeInterval deltaTime;

- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDisplayLink:(DCDDisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;

@end

Expand Down
4 changes: 2 additions & 2 deletions React/Base/RCTFrameUpdate.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <QuartzCore/CADisplayLink.h>
#import "DCDDisplayLink.h"

#import "RCTFrameUpdate.h"

Expand All @@ -17,7 +17,7 @@ @implementation RCTFrameUpdate

RCT_NOT_IMPLEMENTED(- (instancetype)init)

- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
- (instancetype)initWithDisplayLink:(DCDDisplayLink *)displayLink
{
if ((self = [super init])) {
_timestamp = displayLink.timestamp;
Expand Down
6 changes: 6 additions & 0 deletions React/React.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,7 @@
C6827DF71EF17CCC00D66BEF /* RCTJSEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = C6827DF51EF17CCC00D66BEF /* RCTJSEnvironment.h */; };
C6827DFB1EF1800E00D66BEF /* RCTJSEnvironment.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = C6827DF51EF17CCC00D66BEF /* RCTJSEnvironment.h */; };
C6827DFC1EF1801B00D66BEF /* RCTJSEnvironment.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = C6827DF51EF17CCC00D66BEF /* RCTJSEnvironment.h */; };
CCE0428A1F72FC39006492D0 /* DCDDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = C19A08C91CEE3F6700DA4940 /* DCDDisplayLink.m */; };
CF2731C01E7B8DE40044CA4F /* RCTDeviceInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CF2731BE1E7B8DE40044CA4F /* RCTDeviceInfo.h */; };
CF2731C11E7B8DE40044CA4F /* RCTDeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CF2731BF1E7B8DE40044CA4F /* RCTDeviceInfo.m */; };
CF2731C21E7B8DEF0044CA4F /* RCTDeviceInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CF2731BE1E7B8DE40044CA4F /* RCTDeviceInfo.h */; };
Expand Down Expand Up @@ -2015,6 +2016,8 @@
B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTI18nManager.m; sourceTree = "<group>"; };
B95154301D1B34B200FE7B80 /* RCTActivityIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTActivityIndicatorView.h; sourceTree = "<group>"; };
B95154311D1B34B200FE7B80 /* RCTActivityIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorView.m; sourceTree = "<group>"; };
C19A08C81CEE3F6700DA4940 /* DCDDisplayLink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DCDDisplayLink.h; sourceTree = "<group>"; };
C19A08C91CEE3F6700DA4940 /* DCDDisplayLink.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DCDDisplayLink.m; sourceTree = "<group>"; };
C6194AA91EF156280034D062 /* RCTPackagerConnectionBridgeConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPackagerConnectionBridgeConfig.h; sourceTree = "<group>"; };
C6194AAA1EF156280034D062 /* RCTPackagerConnectionBridgeConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPackagerConnectionBridgeConfig.m; sourceTree = "<group>"; };
C6194AAB1EF156280034D062 /* RCTPackagerConnectionConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPackagerConnectionConfig.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2634,6 +2637,8 @@
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
C19A08C81CEE3F6700DA4940 /* DCDDisplayLink.h */,
C19A08C91CEE3F6700DA4940 /* DCDDisplayLink.m */,
);
path = Base;
sourceTree = "<group>";
Expand Down Expand Up @@ -3832,6 +3837,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CCE0428A1F72FC39006492D0 /* DCDDisplayLink.m in Sources */,
13134C9A1E296B2A00B9F3CB /* RCTCxxMethod.mm in Sources */,
59FBEFB61E46D91C0095D885 /* RCTScrollContentViewManager.m in Sources */,
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
Expand Down

0 comments on commit 62a487a

Please sign in to comment.