209 lines
5.7 KiB
Objective-C
209 lines
5.7 KiB
Objective-C
// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
#import "RCTTiming.h"
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTSparseArray.h"
|
|
#import "RCTUtils.h"
|
|
|
|
@interface RCTTimer : NSObject
|
|
|
|
@property (nonatomic, strong, readonly) NSDate *target;
|
|
@property (nonatomic, assign, readonly) BOOL repeats;
|
|
@property (nonatomic, copy, readonly) NSNumber *callbackID;
|
|
@property (nonatomic, assign, readonly) NSTimeInterval interval;
|
|
|
|
@end
|
|
|
|
@implementation RCTTimer
|
|
|
|
- (instancetype)initWithCallbackID:(NSNumber *)callbackID
|
|
interval:(NSTimeInterval)interval
|
|
targetTime:(NSTimeInterval)targetTime
|
|
repeats:(BOOL)repeats
|
|
{
|
|
if ((self = [super init])) {
|
|
_interval = interval;
|
|
_repeats = repeats;
|
|
_callbackID = callbackID;
|
|
_target = [NSDate dateWithTimeIntervalSinceNow:targetTime];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Returns `YES` if we should invoke the JS callback.
|
|
*/
|
|
- (BOOL)updateFoundNeedsJSUpdate
|
|
{
|
|
if (_target && _target.timeIntervalSinceNow <= 0) {
|
|
// The JS Timers will do fine grained calculating of expired timeouts.
|
|
_target = _repeats ? [NSDate dateWithTimeIntervalSinceNow:_interval] : nil;
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTTiming
|
|
{
|
|
RCTSparseArray *_timers;
|
|
id _updateTimer;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
+ (NSArray *)JSMethods
|
|
{
|
|
return @[@"RCTJSTimers.callTimers"];
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
|
|
_timers = [[RCTSparseArray alloc] init];
|
|
|
|
for (NSString *name in @[UIApplicationWillResignActiveNotification,
|
|
UIApplicationDidEnterBackgroundNotification,
|
|
UIApplicationWillTerminateNotification]) {
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(stopTimers)
|
|
name:name
|
|
object:nil];
|
|
}
|
|
|
|
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
|
|
UIApplicationWillEnterForegroundNotification]) {
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(startTimers)
|
|
name:name
|
|
object:nil];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return _bridge != nil;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
[self stopTimers];
|
|
_bridge = nil;
|
|
}
|
|
|
|
- (void)stopTimers
|
|
{
|
|
[_updateTimer invalidate];
|
|
_updateTimer = nil;
|
|
}
|
|
|
|
- (void)startTimers
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
if (![self isValid] || _updateTimer != nil || _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];
|
|
}
|
|
}
|
|
|
|
- (void)update
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
NSMutableArray *timersToCall = [[NSMutableArray alloc] init];
|
|
for (RCTTimer *timer in _timers.allObjects) {
|
|
if ([timer updateFoundNeedsJSUpdate]) {
|
|
[timersToCall addObject:timer.callbackID];
|
|
}
|
|
if (!timer.target) {
|
|
_timers[timer.callbackID] = nil;
|
|
}
|
|
}
|
|
|
|
// call timers that need to be called
|
|
if ([timersToCall count] > 0) {
|
|
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* There's a small difference between the time when we call
|
|
* setTimeout/setInterval/requestAnimation frame and the time it actually makes
|
|
* it here. This is important and needs to be taken into account when
|
|
* calculating the timer's target time. We calculate this by passing in
|
|
* Date.now() from JS and then subtracting that from the current time here.
|
|
*/
|
|
- (void)createTimer:(NSNumber *)callbackID
|
|
duration:(double)jsDuration
|
|
jsSchedulingTime:(double)jsSchedulingTime
|
|
repeats:(BOOL)repeats
|
|
{
|
|
RCT_EXPORT();
|
|
|
|
NSTimeInterval interval = jsDuration / 1000;
|
|
NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime / 1000;
|
|
NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970];
|
|
NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch;
|
|
if (jsSchedulingOverhead < 0) {
|
|
RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
|
|
}
|
|
|
|
NSTimeInterval targetTime = interval - jsSchedulingOverhead;
|
|
if (interval < 0.018) { // Make sure short intervals run each frame
|
|
interval = 0;
|
|
}
|
|
|
|
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
|
|
interval:interval
|
|
targetTime:targetTime
|
|
repeats:repeats];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
_timers[callbackID] = timer;
|
|
[self startTimers];
|
|
});
|
|
}
|
|
|
|
- (void)deleteTimer:(NSNumber *)timerID
|
|
{
|
|
RCT_EXPORT();
|
|
|
|
if (timerID) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
_timers[timerID] = nil;
|
|
if (_timers.count == 0) {
|
|
[self stopTimers];
|
|
}
|
|
});
|
|
} else {
|
|
RCTLogWarn(@"Called deleteTimer: with a nil timerID");
|
|
}
|
|
}
|
|
|
|
@end
|