216 lines
5.6 KiB
Objective-C
216 lines
5.6 KiB
Objective-C
/**
|
|
* 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.
|
|
*/
|
|
|
|
#import "RCTTiming.h"
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTSparseArray.h"
|
|
#import "RCTUtils.h"
|
|
|
|
@interface RCTBridge (Private)
|
|
|
|
/**
|
|
* Allow super fast, one time, timers to skip the queue and be directly executed
|
|
*/
|
|
- (void)_immediatelyCallTimer:(NSNumber *)timer;
|
|
|
|
@end
|
|
|
|
@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;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
@synthesize paused = _paused;
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
_paused = YES;
|
|
_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];
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return RCTJSThread;
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
return _bridge != nil;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
[self stopTimers];
|
|
_bridge = nil;
|
|
}
|
|
|
|
- (void)stopTimers
|
|
{
|
|
_paused = YES;
|
|
}
|
|
|
|
- (void)startTimers
|
|
{
|
|
if (![self isValid] || _timers.count == 0) {
|
|
return;
|
|
}
|
|
|
|
_paused = NO;
|
|
}
|
|
|
|
- (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
|
|
{
|
|
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:@"JSTimersExecution.callTimers" args:@[timersToCall]];
|
|
}
|
|
|
|
if (_timers.count == 0) {
|
|
[self stopTimers];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID
|
|
duration:(NSTimeInterval)jsDuration
|
|
jsSchedulingTime:(NSDate *)jsSchedulingTime
|
|
repeats:(BOOL)repeats)
|
|
{
|
|
if (jsDuration == 0 && repeats == NO) {
|
|
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
|
|
[_bridge _immediatelyCallTimer:callbackID];
|
|
return;
|
|
}
|
|
|
|
NSTimeInterval jsSchedulingOverhead = -jsSchedulingTime.timeIntervalSinceNow;
|
|
if (jsSchedulingOverhead < 0) {
|
|
RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
|
|
|
|
/**
|
|
* Probably debugging on device, set to 0 so we don't ignore the interval
|
|
*/
|
|
jsSchedulingOverhead = 0;
|
|
}
|
|
|
|
NSTimeInterval targetTime = jsDuration - jsSchedulingOverhead;
|
|
if (jsDuration < 0.018) { // Make sure short intervals run each frame
|
|
jsDuration = 0;
|
|
}
|
|
|
|
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
|
|
interval:jsDuration
|
|
targetTime:targetTime
|
|
repeats:repeats];
|
|
_timers[callbackID] = timer;
|
|
[self startTimers];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID)
|
|
{
|
|
if (timerID) {
|
|
_timers[timerID] = nil;
|
|
if (_timers.count == 0) {
|
|
[self stopTimers];
|
|
}
|
|
} else {
|
|
RCTLogWarn(@"Called deleteTimer: with a nil timerID");
|
|
}
|
|
}
|
|
|
|
@end
|