From be12c321f40f58782f103a526937df3f06e773af Mon Sep 17 00:00:00 2001 From: James Reggio Date: Tue, 2 Jan 2018 12:49:05 -0500 Subject: [PATCH] Fire timers in the background exclusively via NSTimer --- React/Modules/RCTTiming.m | 40 ++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 3a2474af806f54..54c40e9065e35d 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -96,6 +96,7 @@ @implementation RCTTiming NSMutableDictionary *_timers; NSTimer *_sleepTimer; BOOL _sendIdleEvents; + BOOL _inBackground; } @synthesize bridge = _bridge; @@ -110,12 +111,13 @@ - (void)setBridge:(RCTBridge *)bridge _paused = YES; _timers = [NSMutableDictionary new]; + _inBackground = NO; for (NSString *name in @[UIApplicationWillResignActiveNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationWillTerminateNotification]) { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(stopTimers) + selector:@selector(appDidMoveToBackground) name:name object:nil]; } @@ -123,7 +125,7 @@ - (void)setBridge:(RCTBridge *)bridge for (NSString *name in @[UIApplicationDidBecomeActiveNotification, UIApplicationWillEnterForegroundNotification]) { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(startTimers) + selector:@selector(appDidMoveToForeground) name:name object:nil]; } @@ -148,8 +150,29 @@ - (void)invalidate _bridge = nil; } +- (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 { + if (_inBackground) { + return; + } + if (!_paused) { _paused = YES; if (_pauseCallback) { @@ -160,7 +183,7 @@ - (void)stopTimers - (void)startTimers { - if (!_bridge || ![self hasPendingTimers]) { + if (!_bridge || _inBackground || ![self hasPendingTimers]) { return; } @@ -225,7 +248,11 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update // 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. - if (!_sendIdleEvents && timersToCall.count == 0) { + if (_inBackground) { + if (_timers.count) { + [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 if (_timers.count == 0) { @@ -295,7 +322,10 @@ - (void)timerDidFire targetTime:targetTime repeats:repeats]; _timers[callbackID] = timer; - if (_paused) { + + if (_inBackground) { + [self scheduleSleepTimer:timer.target]; + } else if (_paused) { if ([timer.target timeIntervalSinceNow] > kMinimumSleepInterval) { [self scheduleSleepTimer:timer.target]; } else {