@ -23,7 +23,11 @@ public enum SessionBackgroundTaskState {
// MARK: - S e s s i o n B a c k g r o u n d T a s k M a n a g e r
public class SessionBackgroundTaskManager {
// / M a x i m u m d u r a t i o n t o e x t e n d b a c k g r o u n d t a s k s
private static let maxBackgroundTime : TimeInterval = 180
private let dependencies : Dependencies
private let queue = DispatchQueue ( label : " com.session.backgroundTaskManager " )
// / T h i s p r o p e r t y s h o u l d o n l y b e a c c e s s e d w h i l e s y n c h r o n i z e d o n t h i s i n s t a n c e .
private var backgroundTaskId : UIBackgroundTaskIdentifier = . invalid
@ -43,11 +47,11 @@ public class SessionBackgroundTaskManager {
// / b e g i n s , w e u s e a s i n g l e u n i n t e r r u p t e d b a c k g r o u n d t h a t s p a n s t h e i r l i f e t i m e s .
// /
// / T h i s p r o p e r t y s h o u l d o n l y b e a c c e s s e d w h i l e s y n c h r o n i z e d o n t h i s i n s t a n c e .
private var continuityTimer : Timer?
private var continuityTimer : DispatchSource Timer?
// / I n o r d e r t o e n s u r e w e h a v e s u f f i c i e n t t i m e t o c l e a n u p b e f o r e b a c k g r o u n d t a s k s e x p i r e ( w i t h o u t h a v i n g t o k i c k o f f a d d i t i o n a l t a s k s )
// / w e t r a c k t h e r e m a i n i n g b a c k g r o u n d e x e c u t i o n t i m e a n d e n d t a s k s 5 s e c o n d s e a r l y ( s a m e a s t h e A p p D e l e g a t e b a c k g r o u n d f e t c h )
private var expirationTimeObserver : Timer?
private var expirationTimeObserver : DispatchSource Timer?
private var hasGottenValidBackgroundTimeRemaining : Bool = false
fileprivate init ( using dependencies : Dependencies ) {
@ -61,13 +65,6 @@ public class SessionBackgroundTaskManager {
// MARK: - F u n c t i o n s
@ discardableResult private static func synced < T > ( _ lock : Any , closure : ( ) -> T ) -> T {
objc_sync_enter ( lock )
let result : T = closure ( )
objc_sync_exit ( lock )
return result
}
public func startObservingNotifications ( ) {
guard dependencies [ singleton : . appContext ] . isMainApp else { return }
@ -86,14 +83,14 @@ public class SessionBackgroundTaskManager {
}
@objc private func applicationDidBecomeActive ( ) {
SessionBackgroundTaskManager. synced ( self ) { [ weak self ] in
queue. sync { [ weak self ] in
self ? . isAppActive = true
self ? . ensureBackgroundTaskState ( )
}
}
@objc private func applicationWillResignActive ( ) {
SessionBackgroundTaskManager. synced ( self ) { [ weak self ] in
queue. sync { [ weak self ] in
self ? . isAppActive = false
self ? . ensureBackgroundTaskState ( )
}
@ -109,7 +106,7 @@ public class SessionBackgroundTaskManager {
// b a c k g r o u n d t a s k , b u t t h e b a c k g r o u n d t a s k c o u l d n ' t b e b e g u n .
// I n t h a t c a s e e x p i r a t i o n B l o c k w i l l n o t b e c a l l e d .
fileprivate func addTask ( expiration : @ escaping ( ) -> ( ) ) -> UInt64 ? {
return SessionBackgroundTaskManager. synced ( self ) { [ weak self , dependencies ] in
return queue. sync { [ weak self ] ( ) -> UInt64 ? in
let taskId : UInt64 = ( ( self ? . idCounter ? ? 0 ) + 1 )
self ? . idCounter = taskId
self ? . expirationMap [ taskId ] = expiration
@ -118,18 +115,15 @@ public class SessionBackgroundTaskManager {
self ? . expirationMap . removeValue ( forKey : taskId )
}
self ? . continuityTimer ? . invalidate ( )
self ? . continuityTimer = nil
if self ? . continuityTimer != nil {
self ? . continuityTimer ? . cancel ( )
self ? . continuityTimer = nil
}
// S t a r t o b s e r v i n g t h e b a c k g r o u n d t i m e r e m a i n i n g
if self ? . expirationTimeObserver ? . is Valid ! = true {
if self ? . expirationTimeObserver ? . is Cancelled = = true {
self ? . hasGottenValidBackgroundTimeRemaining = false
self ? . expirationTimeObserver = Timer . scheduledTimerOnMainThread (
withTimeInterval : 1 ,
repeats : true ,
using : dependencies ,
block : { _ in self ? . expirationTimerDidFire ( ) }
)
self ? . checkExpirationTime ( in : . seconds ( 1 ) ) // D o n ' t k n o w t h e r e m a i n i n g t i m e s o c h e c k s o o n
}
return taskId
@ -139,7 +133,7 @@ public class SessionBackgroundTaskManager {
fileprivate func removeTask ( taskId : UInt64 ? ) {
guard let taskId : UInt64 = taskId else { return }
SessionBackgroundTaskManager. synced ( self ) { [ weak self , dependencies ] in
queue. sync { [ weak self , queue ] in
self ? . expirationMap . removeValue ( forKey : taskId )
// T h i s t i m e r w i l l e n s u r e t h a t w e k e e p t h e b a c k g r o u n d t a s k a c t i v e ( i f n e c e s s a r y )
@ -148,87 +142,93 @@ public class SessionBackgroundTaskManager {
// s h o u l d b e a b l e t o e n s u r e b a c k g r o u n d t a s k s b y " n a r r o w l y " w r a p p i n g
// t h e i r c o r e l o g i c w i t h a S e s s i o n B a c k g r o u n d T a s k a n d n o t w o r r y i n g a b o u t " h a n d o f f "
// b e t w e e n S e s s i o n B a c k g r o u n d T a s k s .
self ? . continuityTimer ? . invalidate ( )
self ? . continuityTimer = Timer . scheduledTimerOnMainThread (
withTimeInterval : 0.25 ,
using : dependencies ,
block : { _ in self ? . continuityTimerDidFire ( ) }
)
if self ? . continuityTimer != nil {
self ? . continuityTimer ? . cancel ( )
self ? . continuityTimer = nil
}
self ? . continuityTimer = DispatchSource . makeTimerSource ( queue : queue )
self ? . continuityTimer ? . schedule ( deadline : . now ( ) + . milliseconds ( 250 ) )
self ? . continuityTimer ? . setEventHandler { self ? . continuityTimerDidFire ( ) }
self ? . continuityTimer ? . resume ( )
self ? . ensureBackgroundTaskState ( )
}
}
// / B e g i n s o r e n d a b a c k g r o u n d t a s k i f n e c e s s a r y .
// / B e g i n s o r e n d a b a c k g r o u n d t a s k i f n e c e s s a r y
// /
// / * * N o t e : * * S h o u l d o n l y b e c a l l e d i n t e r n a l l y w i t h i n ` q u e u e . s y n c ` f o r t h r e a d s a f e t y
@ discardableResult private func ensureBackgroundTaskState ( ) -> Bool {
// W e c a n ' t c r e a t e b a c k g r o u n d t a s k s i n t h e S A E , b u t p r e t e n d t h a t w e s u c c e e d e d .
guard dependencies [ singleton : . appContext ] . isMainApp else { return true }
return SessionBackgroundTaskManager . synced ( self ) { [ weak self , dependencies ] in
// W e o n l y w a n t t o h a v e a b a c k g r o u n d t a s k i f w e a r e :
// a ) " n o t a c t i v e " A N D
// b 1 ) t h e r e i s o n e o r m o r e a c t i v e i n s t a n c e o f S e s s i o n B a c k g r o u n d T a s k O R . . .
// b 2 ) . . . t h e r e _ w a s _ a n a c t i v e i n s t a n c e r e c e n t l y .
let shouldHaveBackgroundTask : Bool = (
self ? . isAppActive = = false && (
( self ? . expirationMap . count ? ? 0 ) > 0 ||
self ? . continuityTimer != nil
)
// W e o n l y w a n t t o h a v e a b a c k g r o u n d t a s k i f w e a r e :
// a ) " n o t a c t i v e " A N D
// b 1 ) t h e r e i s o n e o r m o r e a c t i v e i n s t a n c e o f S e s s i o n B a c k g r o u n d T a s k O R . . .
// b 2 ) . . . t h e r e _ w a s _ a n a c t i v e i n s t a n c e r e c e n t l y .
let shouldHaveBackgroundTask : Bool = (
self . isAppActive = = false && (
self . expirationMap . count > 0 ||
self . continuityTimer != nil
)
let hasBackgroundTask : Bool = ( self ? . backgroundTaskId != . invalid )
guard shouldHaveBackgroundTask != hasBackgroundTask else {
// C u r r e n t s t a t e i s c o r r e c t
return true
}
guard ! shouldHaveBackgroundTask else {
return ( self ? . startBackgroundTask ( ) = = true )
}
// N e e d t o e n d b a c k g r o u n d t a s k .
let maybeBackgroundTaskId : UIBackgroundTaskIdentifier ? = self ? . backgroundTaskId
self ? . backgroundTaskId = . invalid
self ? . expirationTimeObserver ? . invalidate ( )
self ? . expirationTimeObserver = nil
if let backgroundTaskId : UIBackgroundTaskIdentifier = maybeBackgroundTaskId , backgroundTaskId != . invalid {
dependencies [ singleton : . appContext ] . endBackgroundTask ( backgroundTaskId )
}
)
let hasBackgroundTask : Bool = ( self . backgroundTaskId != . invalid )
guard shouldHaveBackgroundTask != hasBackgroundTask else {
// C u r r e n t s t a t e i s c o r r e c t
return true
}
guard ! shouldHaveBackgroundTask else {
return ( self . startOverarchingBackgroundTask ( ) = = true )
}
// N e e d t o e n d b a c k g r o u n d t a s k .
let maybeBackgroundTaskId : UIBackgroundTaskIdentifier ? = self . backgroundTaskId
self . backgroundTaskId = . invalid
if self . expirationTimeObserver != nil {
self . expirationTimeObserver ? . cancel ( )
self . expirationTimeObserver = nil
}
if let backgroundTaskId : UIBackgroundTaskIdentifier = maybeBackgroundTaskId , backgroundTaskId != . invalid {
dependencies [ singleton : . appContext ] . endBackgroundTask ( backgroundTaskId )
}
return true
}
// / R e t u r n s ` f a l s e ` i f t h e b a c k g r o u n d t a s k c a n n o t b e b e g u n .
private func startBackgroundTask ( ) -> Bool {
// / R e t u r n s ` f a l s e ` i f t h e b a c k g r o u n d t a s k c a n n o t b e b e g u n
// /
// / * * N o t e : * * S h o u l d o n l y b e c a l l e d i n t e r n a l l y w i t h i n ` q u e u e . s y n c ` f o r t h r e a d s a f e t y
private func startOverarchingBackgroundTask ( ) -> Bool {
guard dependencies [ singleton : . appContext ] . isMainApp else { return false }
return SessionBackgroundTaskManager . synced ( self ) { [ weak self , dependencies ] in
self ? . backgroundTaskId = dependencies [ singleton : . appContext ] . beginBackgroundTask {
// / S u p p o s e d l y ` [ U I A p p l i c a t i o n b e g i n B a c k g r o u n d T a s k W i t h E x p i r a t i o n H a n d l e r ] ` ' s h a n d l e r
// / w i l l a l w a y s b e c a l l e d o n t h e m a i n t h r e a d , b u t i n p r a c t i c e w e ' v e o b s e r v e d o t h e r w i s e .
// /
// / S e e :
// / h t t p s : / / d e v e l o p e r . a p p l e . c o m / d o c u m e n t a t i o n / u i k i t / u i a p p l i c a t i o n / 1 6 2 3 0 3 1 - b e g i n b a c k g r o u n d t a s k w i t h e x p i r a t i o )
self . backgroundTaskId = dependencies [ singleton : . appContext ] . beginBackgroundTask { [ weak self ] in
// / S u p p o s e d l y ` [ U I A p p l i c a t i o n b e g i n B a c k g r o u n d T a s k W i t h E x p i r a t i o n H a n d l e r ] ` ' s h a n d l e r
// / w i l l a l w a y s b e c a l l e d o n t h e m a i n t h r e a d , b u t i n p r a c t i c e w e ' v e o b s e r v e d o t h e r w i s e .
// /
// / S e e :
// / h t t p s : / / d e v e l o p e r . a p p l e . c o m / d o c u m e n t a t i o n / u i k i t / u i a p p l i c a t i o n / 1 6 2 3 0 3 1 - b e g i n b a c k g r o u n d t a s k w i t h e x p i r a t i o )
self ? . queue . sync {
self ? . backgroundTaskExpired ( )
}
// I f t h e b a c k g r o u n d t a s k c o u l d n o t b e g i n , r e t u r n f a l s e t o i n d i c a t e t h a t
return ( self ? . backgroundTaskId != . invalid )
}
// I f t h e b a c k g r o u n d t a s k c o u l d n o t b e g i n , r e t u r n f a l s e t o i n d i c a t e t h a t
return ( self . backgroundTaskId != . invalid )
}
// / * * N o t e : * * S h o u l d o n l y b e c a l l e d i n t e r n a l l y w i t h i n ` q u e u e . s y n c ` f o r t h r e a d s a f e t y
private func backgroundTaskExpired ( ) {
var backgroundTaskId : UIBackgroundTaskIdentifier = . invalid
var expirationMap : [ UInt64 : ( ) -> ( ) ] = [ : ]
let backgroundTaskId : UIBackgroundTaskIdentifier = self . backgroundTaskId
let expirationMap : [ UInt64 : ( ) -> ( ) ] = self . expirationMap
self . backgroundTaskId = . invalid
self . expirationMap . removeAll ( )
SessionBackgroundTaskManager . synced ( self ) { [ weak self ] in
backgroundTaskId = ( self ? . backgroundTaskId ? ? . invalid )
self ? . backgroundTaskId = . invalid
self ? . expirationTimeObserver ? . invalidate ( )
self ? . expirationTimeObserver = nil
expirationMap = ( self ? . expirationMap ? ? [ : ] )
self ? . expirationMap . removeAll ( )
if self . expirationTimeObserver != nil {
self . expirationTimeObserver ? . cancel ( )
self . expirationTimeObserver = nil
}
// / S u p p o s e d l y ` [ U I A p p l i c a t i o n b e g i n B a c k g r o u n d T a s k W i t h E x p i r a t i o n H a n d l e r ] ` ' s h a n d l e r
@ -247,33 +247,44 @@ public class SessionBackgroundTaskManager {
}
}
private func checkExpirationTime ( in interval : DispatchTimeInterval ) {
expirationTimeObserver = DispatchSource . makeTimerSource ( queue : queue )
expirationTimeObserver ? . schedule ( deadline : . now ( ) + interval )
expirationTimeObserver ? . setEventHandler { [ weak self ] in self ? . expirationTimerDidFire ( ) }
expirationTimeObserver ? . resume ( )
}
// / T i m e r w i l l a l w a y s f i r e o n t h e ` q u e u e ` s o n o n e e d t o ` q u e u e . s y n c `
private func continuityTimerDidFire ( ) {
SessionBackgroundTaskManager . synced ( self ) { [ weak self ] in
self ? . continuityTimer ? . invalidate ( )
self ? . continuityTimer = nil
self ? . ensureBackgroundTaskState ( )
}
continuityTimer = nil
ensureBackgroundTaskState ( )
}
// / T i m e r w i l l a l w a y s f i r e o n t h e ` q u e u e ` s o n o n e e d t o ` q u e u e . s y n c `
private func expirationTimerDidFire ( ) {
expirationTimeObserver = nil
guard dependencies [ singleton : . appContext ] . isMainApp else { return }
let backgroundTimeRemaining : TimeInterval = dependencies [ singleton : . appContext ] . backgroundTimeRemaining
SessionBackgroundTaskManager . synced ( self ) { [ weak self ] in
// I t t a k e s t h e O S a l i t t l e w h i l e t o u p d a t e t h e ' b a c k g r o u n d T i m e R e m a i n i n g ' v a l u e s o i f i t h a s n ' t b e e n u p d a t e d
// y e t t h e n d o n ' t d o a n y t h i n g
guard self ? . hasGottenValidBackgroundTimeRemaining = = true || backgroundTimeRemaining != . greatestFiniteMagnitude else {
return
}
self ? . hasGottenValidBackgroundTimeRemaining = true
// I f t h e r e i s m o r e t h a n 5 s e c o n d s r e m a i n i n g t h e n n o n e e d t o d o a n y t h i n g y e t ( p l e n t y o f t i m e t o c o n t i n u e r u n n i n g )
guard backgroundTimeRemaining <= 5 else { return }
// T h e r e i s n ' t a l o t o f t i m e r e m a i n i n g s o t r i g g e r t h e e x p i r a t i o n
self ? . backgroundTaskExpired ( )
// / I t t a k e s t h e O S a l i t t l e w h i l e t o u p d a t e t h e ' b a c k g r o u n d T i m e R e m a i n i n g ' v a l u e s o i f i t h a s n ' t b e e n u p d a t e d y e t t h e n d o n ' t d o a n y t h i n g
guard self . hasGottenValidBackgroundTimeRemaining = = true || backgroundTimeRemaining != . greatestFiniteMagnitude else {
self . checkExpirationTime ( in : . seconds ( 1 ) )
return
}
self . hasGottenValidBackgroundTimeRemaining = true
switch backgroundTimeRemaining {
// / T h e r e i s m o r e t h a n 1 0 s e c o n d s r e m a i n i n g s o n o n e e d t o d o a n y t h i n g y e t ( p l e n t y o f t i m e t o c o n t i n u e r u n n i n g )
case 10. . . : self . checkExpirationTime ( in : . seconds ( 5 ) )
// / T h e r e i s b e t w e e n 5 a n d 1 0 s e c o n d s s o p o l l m o r e f r e q u e n t l y j u s t i n c a s e
case 5. . < 10 : self . checkExpirationTime ( in : . milliseconds ( 2500 ) )
// / T h e r e i s n ' t a l o t o f t i m e r e m a i n i n g s o t r i g g e r t h e e x p i r a t i o n
default : self . backgroundTaskExpired ( )
}
}
}
@ -282,8 +293,6 @@ public class SessionBackgroundTaskManager {
public class SessionBackgroundTask {
private let dependencies : Dependencies
// / T h i s p r o p e r t y s h o u l d o n l y b e a c c e s s e d w h i l e s y n c h r o n i z e d o n t h i s i n s t a n c e
private var taskId : UInt64 ?
private let label : String
private var completion : ( ( SessionBackgroundTaskState ) -> ( ) ) ?
@ -308,86 +317,31 @@ public class SessionBackgroundTask {
// MARK: - F u n c t i o n s
@ discardableResult private static func synced < T > ( _ lock : Any , closure : ( ) -> T ) -> T {
objc_sync_enter ( lock )
let result : T = closure ( )
objc_sync_exit ( lock )
return result
}
private func startBackgroundTask ( ) {
// M a k e a l o c a l c o p y o f c o m p l e t i o n t o e n s u r e t h a t i t i s c a l l e d e x a c t l y o n c e
var completion : ( ( SessionBackgroundTaskState ) -> ( ) ) ?
self . taskId = dependencies [ singleton : . backgroundTaskManager ] . addTask { [ weak self ] in
Threading . dispatchMainThreadSafe {
guard let strongSelf = self else { return }
SessionBackgroundTask . synced ( strongSelf ) {
self ? . taskId = nil
completion = self ? . completion
self ? . completion = nil
}
completion ? ( . expired )
}
}
// I f w e d i d n ' t g e t a t a s k I d t h e n t h e b a c k g r o u n d t a s k c o u l d n o t b e s t a r t e d s o
// w e s h o u l d c a l l t h e c o m p l e t i o n b l o c k w i t h a ' c o u l d N o t S t a r t ' e r r o r
guard taskId = = nil else { return }
SessionBackgroundTask . synced ( self ) { [ weak self ] in
completion = self ? . completion
self ? . completion = nil
taskId = dependencies [ singleton : . backgroundTaskManager ] . addTask { [ weak self ] in
self ? . taskExpired ( )
}
if completion != nil {
Threading . dispatchMainThreadSafe {
completion ? ( . couldNotStart )
}
if taskId = = nil {
completion ? ( . couldNotStart )
completion = nil
}
}
public func cancel ( ) {
guard taskId != nil else { return }
// M a k e a l o c a l c o p y o f c o m p l e t i o n t o e n s u r e t h a t i t i s c a l l e d e x a c t l y o n c e
var completion : ( ( SessionBackgroundTaskState ) -> ( ) ) ?
SessionBackgroundTask . synced ( self ) { [ weak self , dependencies ] in
dependencies [ singleton : . backgroundTaskManager ] . removeTask ( taskId : self ? . taskId )
completion = self ? . completion
self ? . taskId = nil
self ? . completion = nil
}
// e n d B a c k g r o u n d T a s k m u s t b e c a l l e d o n t h e m a i n t h r e a d .
if completion != nil {
Threading . dispatchMainThreadSafe {
completion ? ( . cancelled )
}
}
dependencies [ singleton : . backgroundTaskManager ] . removeTask ( taskId : taskId )
completion ? ( . cancelled )
completion = nil
}
private func endBackgroundTask ( ) {
guard taskId != nil else { return }
// M a k e a l o c a l c o p y o f c o m p l e t i o n s i n c e t h i s m e t h o d i s c a l l e d b y ` d e a l l o c `
var completion : ( ( SessionBackgroundTaskState ) -> ( ) ) ?
SessionBackgroundTask . synced ( self ) { [ weak self , dependencies ] in
dependencies [ singleton : . backgroundTaskManager ] . removeTask ( taskId : self ? . taskId )
completion = self ? . completion
self ? . taskId = nil
self ? . completion = nil
}
// e n d B a c k g r o u n d T a s k m u s t b e c a l l e d o n t h e m a i n t h r e a d .
if completion != nil {
Threading . dispatchMainThreadSafe {
completion ? ( . cancelled )
}
}
cancel ( )
}
private func taskExpired ( ) {
completion ? ( . expired )
completion = nil
}
}