react-native-macos/React/CoreModules/RCTTiming.mm

416 строки
11 KiB
Plaintext
Исходник Обычный вид История

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTiming.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTAssert.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import "CoreModulesPlugins.h"
static const NSTimeInterval kMinimumSleepInterval = 1;
// These timing contants should be kept in sync with the ones in `JSTimers.js`.
// The duration of a frame. This assumes that we want to run at 60 fps.
static const NSTimeInterval kFrameDuration = 1.0 / 60.0;
// The minimum time left in a frame to trigger the idle callback.
static const NSTimeInterval kIdleCallbackFrameDeadline = 0.001;
@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)shouldFire:(NSDate *)now
{
if (_target && [_target timeIntervalSinceDate:now] <= 0) {
return YES;
}
return NO;
}
- (void)reschedule
{
// The JS Timers will do fine grained calculating of expired timeouts.
_target = [NSDate dateWithTimeIntervalSinceNow:_interval];
}
@end
@interface _RCTTimingProxy : NSObject
@end
// NSTimer retains its target, insert this class to break potential retain cycles
@implementation _RCTTimingProxy {
__weak id _target;
}
+ (instancetype)proxyWithTarget:(id)target
{
_RCTTimingProxy *proxy = [self new];
if (proxy) {
proxy->_target = target;
}
return proxy;
}
- (void)timerDidFire
{
[_target timerDidFire];
}
@end
@implementation RCTTiming {
NSMutableDictionary<NSNumber *, _RCTTimer *> *_timers;
NSTimer *_sleepTimer;
BOOL _sendIdleEvents;
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
BOOL _inBackground;
id<RCTTimingDelegate> _timingDelegate;
}
@synthesize bridge = _bridge;
@synthesize paused = _paused;
@synthesize pauseCallback = _pauseCallback;
2015-04-08 18:52:48 +03:00
RCT_EXPORT_MODULE()
- (instancetype)initWithDelegate:(id<RCTTimingDelegate>)delegate
{
if (self = [super init]) {
[self setup];
_timingDelegate = delegate;
}
return self;
}
- (void)initialize
{
[self setup];
}
- (void)setup
{
_paused = YES;
_timers = [NSMutableDictionary new];
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
_inBackground = NO;
for (NSString *name in @[
UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification
]) {
[[NSNotificationCenter defaultCenter] addObserver:self
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
selector:@selector(appDidMoveToBackground)
name:name
object:nil];
}
for (NSString *name in @[ UIApplicationDidBecomeActiveNotification, UIApplicationWillEnterForegroundNotification ]) {
[[NSNotificationCenter defaultCenter] addObserver:self
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
selector:@selector(appDidMoveToForeground)
name:name
object:nil];
}
}
- (void)dealloc
{
[_sleepTimer invalidate];
}
- (dispatch_queue_t)methodQueue
{
return RCTJSThread;
}
- (void)invalidate
{
[self stopTimers];
_bridge = nil;
_timingDelegate = nil;
}
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
- (void)appDidMoveToBackground
{
// Deactivate the CADisplayLink while in the background.
[self stopTimers];
_inBackground = YES;
// Issue one final timer callback, which will schedule a
// background NSTimer, if needed.
[self didUpdateFrame:nil];
}
- (void)appDidMoveToForeground
{
_inBackground = NO;
[self startTimers];
}
- (void)stopTimers
{
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
if (_inBackground) {
return;
}
if (!_paused) {
_paused = YES;
if (_pauseCallback) {
_pauseCallback();
}
}
}
- (void)startTimers
{
if ((!_bridge && !_timingDelegate) || _inBackground || ![self hasPendingTimers]) {
return;
}
if (_paused) {
_paused = NO;
if (_pauseCallback) {
_pauseCallback();
}
}
}
- (BOOL)hasPendingTimers
{
@synchronized(_timers) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
return _sendIdleEvents || _timers.count > 0;
}
}
- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
NSDate *nextScheduledTarget = [NSDate distantFuture];
NSMutableArray<_RCTTimer *> *timersToCall = [NSMutableArray new];
NSDate *now = [NSDate date]; // compare all the timers to the same base time
@synchronized(_timers) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
for (_RCTTimer *timer in _timers.allValues) {
if ([timer shouldFire:now]) {
[timersToCall addObject:timer];
} else {
nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
}
}
}
// Call timers that need to be called
if (timersToCall.count > 0) {
NSArray<NSNumber *> *sortedTimers = [[timersToCall sortedArrayUsingComparator:^(_RCTTimer *a, _RCTTimer *b) {
return [a.target compare:b.target];
}] valueForKey:@"callbackID"];
if (_bridge) {
[_bridge enqueueJSCall:@"JSTimers" method:@"callTimers" args:@[ sortedTimers ] completion:NULL];
} else {
[_timingDelegate callTimers:sortedTimers];
}
}
for (_RCTTimer *timer in timersToCall) {
if (timer.repeats) {
[timer reschedule];
nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
} else {
@synchronized(_timers) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
[_timers removeObjectForKey:timer.callbackID];
}
}
}
if (_sendIdleEvents) {
NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
NSTimeInterval frameElapsed = currentTimestamp - update.timestamp;
if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) {
NSNumber *absoluteFrameStartMS = @((currentTimestamp - frameElapsed) * 1000);
if (_bridge) {
[_bridge enqueueJSCall:@"JSTimers" method:@"callIdleCallbacks" args:@[ absoluteFrameStartMS ] completion:NULL];
} else {
[_timingDelegate callIdleCallbacks:absoluteFrameStartMS];
}
}
}
// Switch to a paused state only if we didn't call any timer this frame, so if
// in response to this timer another timer is scheduled, we don't pause and unpause
// the displaylink frivolously.
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
NSUInteger timerCount;
@synchronized(_timers) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
timerCount = _timers.count;
}
if (_inBackground) {
if (timerCount) {
[self scheduleSleepTimer:nextScheduledTarget];
}
} else if (!_sendIdleEvents && timersToCall.count == 0) {
// No need to call the pauseCallback as RCTDisplayLink will ask us about our paused
// status immediately after completing this call
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
if (timerCount == 0) {
_paused = YES;
}
// If the next timer is more than 1 second out, pause and schedule an NSTimer;
else if ([nextScheduledTarget timeIntervalSinceNow] > kMinimumSleepInterval) {
[self scheduleSleepTimer:nextScheduledTarget];
_paused = YES;
}
}
}
- (void)scheduleSleepTimer:(NSDate *)sleepTarget
{
@synchronized(self) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
if (!_sleepTimer || !_sleepTimer.valid) {
_sleepTimer = [[NSTimer alloc] initWithFireDate:sleepTarget
interval:0
target:[_RCTTimingProxy proxyWithTarget:self]
selector:@selector(timerDidFire)
userInfo:nil
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:_sleepTimer forMode:NSDefaultRunLoopMode];
} else {
_sleepTimer.fireDate = [_sleepTimer.fireDate earlierDate:sleepTarget];
}
}
}
- (void)timerDidFire
{
_sleepTimer = nil;
if (_paused) {
[self startTimers];
// Immediately dispatch frame, so we don't have to wait on the displaylink.
[self didUpdateFrame:nil];
}
}
/**
* A method used for asynchronously creating a timer. If the timer has already expired,
* (based on the provided jsSchedulingTime) then it will be immediately invoked.
*
* 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
: (double)callbackID duration
: (NSTimeInterval)jsDuration jsSchedulingTime
: (double)jsSchedulingTime repeats
: (BOOL)repeats)
{
NSNumber *callbackIdObjc = [NSNumber numberWithDouble:callbackID];
NSDate *schedulingTime = [RCTConvert NSDate:[NSNumber numberWithDouble:jsSchedulingTime]];
if (jsDuration == 0 && repeats == NO) {
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
if (_bridge) {
[_bridge _immediatelyCallTimer:callbackIdObjc];
} else {
[_timingDelegate immediatelyCallTimer:callbackIdObjc];
}
return;
}
[self createTimerForNextFrame:callbackIdObjc duration:jsDuration jsSchedulingTime:schedulingTime repeats:repeats];
}
/**
* A method used for synchronously creating a timer. The timer will not be invoked until the
* next frame, regardless of whether it has already expired (i.e. jsSchedulingTime is 0).
*/
- (void)createTimerForNextFrame:(nonnull NSNumber *)callbackID
duration:(NSTimeInterval)jsDuration
jsSchedulingTime:(NSDate *)jsSchedulingTime
repeats:(BOOL)repeats
{
NSTimeInterval jsSchedulingOverhead = MAX(-jsSchedulingTime.timeIntervalSinceNow, 0);
2015-04-08 18:52:48 +03:00
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];
@synchronized(_timers) {
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
_timers[callbackID] = timer;
}
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
if (_inBackground) {
[self scheduleSleepTimer:timer.target];
} else if (_paused) {
if ([timer.target timeIntervalSinceNow] > kMinimumSleepInterval) {
[self scheduleSleepTimer:timer.target];
} else {
[self startTimers];
}
}
}
RCT_EXPORT_METHOD(deleteTimer : (double)timerID)
{
@synchronized(_timers) {
[_timers removeObjectForKey:[NSNumber numberWithDouble:timerID]];
Fire timers in the background via NSTimer (#23674) Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: https://github.com/facebook/react-native/pull/23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
2019-03-26 22:17:02 +03:00
}
if (![self hasPendingTimers]) {
[self stopTimers];
}
}
RCT_EXPORT_METHOD(setSendIdleEvents : (BOOL)sendIdleEvents)
{
_sendIdleEvents = sendIdleEvents;
if (sendIdleEvents) {
[self startTimers];
} else if (![self hasPendingTimers]) {
[self stopTimers];
}
}
@end
Class RCTTimingCls(void)
{
return RCTTiming.class;
}