Skip to content

Commit be12c32

Browse files
committed
Fire timers in the background exclusively via NSTimer
1 parent 93717e3 commit be12c32

File tree

1 file changed

+35
-5
lines changed

1 file changed

+35
-5
lines changed

React/Modules/RCTTiming.m

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ @implementation RCTTiming
9696
NSMutableDictionary<NSNumber *, _RCTTimer *> *_timers;
9797
NSTimer *_sleepTimer;
9898
BOOL _sendIdleEvents;
99+
BOOL _inBackground;
99100
}
100101

101102
@synthesize bridge = _bridge;
@@ -110,20 +111,21 @@ - (void)setBridge:(RCTBridge *)bridge
110111

111112
_paused = YES;
112113
_timers = [NSMutableDictionary new];
114+
_inBackground = NO;
113115

114116
for (NSString *name in @[UIApplicationWillResignActiveNotification,
115117
UIApplicationDidEnterBackgroundNotification,
116118
UIApplicationWillTerminateNotification]) {
117119
[[NSNotificationCenter defaultCenter] addObserver:self
118-
selector:@selector(stopTimers)
120+
selector:@selector(appDidMoveToBackground)
119121
name:name
120122
object:nil];
121123
}
122124

123125
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
124126
UIApplicationWillEnterForegroundNotification]) {
125127
[[NSNotificationCenter defaultCenter] addObserver:self
126-
selector:@selector(startTimers)
128+
selector:@selector(appDidMoveToForeground)
127129
name:name
128130
object:nil];
129131
}
@@ -148,8 +150,29 @@ - (void)invalidate
148150
_bridge = nil;
149151
}
150152

153+
- (void)appDidMoveToBackground
154+
{
155+
// Deactivate the CADisplayLink while in the background.
156+
[self stopTimers];
157+
_inBackground = YES;
158+
159+
// Issue one final timer callback, which will schedule a
160+
// background NSTimer, if needed.
161+
[self didUpdateFrame:nil];
162+
}
163+
164+
- (void)appDidMoveToForeground
165+
{
166+
_inBackground = NO;
167+
[self startTimers];
168+
}
169+
151170
- (void)stopTimers
152171
{
172+
if (_inBackground) {
173+
return;
174+
}
175+
153176
if (!_paused) {
154177
_paused = YES;
155178
if (_pauseCallback) {
@@ -160,7 +183,7 @@ - (void)stopTimers
160183

161184
- (void)startTimers
162185
{
163-
if (!_bridge || ![self hasPendingTimers]) {
186+
if (!_bridge || _inBackground || ![self hasPendingTimers]) {
164187
return;
165188
}
166189

@@ -225,7 +248,11 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update
225248
// Switch to a paused state only if we didn't call any timer this frame, so if
226249
// in response to this timer another timer is scheduled, we don't pause and unpause
227250
// the displaylink frivolously.
228-
if (!_sendIdleEvents && timersToCall.count == 0) {
251+
if (_inBackground) {
252+
if (_timers.count) {
253+
[self scheduleSleepTimer:nextScheduledTarget];
254+
}
255+
} else if (!_sendIdleEvents && timersToCall.count == 0) {
229256
// No need to call the pauseCallback as RCTDisplayLink will ask us about our paused
230257
// status immediately after completing this call
231258
if (_timers.count == 0) {
@@ -295,7 +322,10 @@ - (void)timerDidFire
295322
targetTime:targetTime
296323
repeats:repeats];
297324
_timers[callbackID] = timer;
298-
if (_paused) {
325+
326+
if (_inBackground) {
327+
[self scheduleSleepTimer:timer.target];
328+
} else if (_paused) {
299329
if ([timer.target timeIntervalSinceNow] > kMinimumSleepInterval) {
300330
[self scheduleSleepTimer:timer.target];
301331
} else {

0 commit comments

Comments
 (0)