diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.h b/SignalServiceKit/src/Util/OWSBackgroundTask.h index dfb8b6dee..283284386 100644 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.h +++ b/SignalServiceKit/src/Util/OWSBackgroundTask.h @@ -10,6 +10,17 @@ typedef NS_ENUM(NSUInteger, BackgroundTaskState) { typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState backgroundTaskState); +// This class makes it easier and safer to use background tasks. +// +// * Uses RAII (Resource Acquisition Is Initialization) pattern. +// * Ensures completion block is called exactly once and on main thread, +// to facilitate handling "background task timed out" case, for example. +// * Ensures we properly handle the "background task could not be created" +// case. +// +// Usage: +// +// * Use factory method to start a background task. @interface OWSBackgroundTask : NSObject - (instancetype)init NS_UNAVAILABLE; diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.m b/SignalServiceKit/src/Util/OWSBackgroundTask.m index a310910a4..2239d6b19 100644 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.m +++ b/SignalServiceKit/src/Util/OWSBackgroundTask.m @@ -4,6 +4,7 @@ #import "OWSBackgroundTask.h" #import "AppContext.h" +#import "Threading.h" @interface OWSBackgroundTask () @@ -12,6 +13,7 @@ // This property should only be accessed while synchronized on this instance. @property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; +// This property should only be accessed while synchronized on this instance. @property (nonatomic, nullable) BackgroundTaskCompletionBlock completionBlock; @end @@ -74,12 +76,14 @@ - (void)startBackgroundTask { - __weak typeof(self) weakSelf = self; // beginBackgroundTaskWithExpirationHandler must be called on the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ + DispatchMainThreadSafe(^{ + __weak typeof(self) weakSelf = self; self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ - OWSAssert([NSThread isMainThread]); - __strong typeof(self) strongSelf = weakSelf; + // Note the usage of OWSCAssert() to avoid capturing a reference to self. + OWSCAssert([NSThread isMainThread]); + + OWSBackgroundTask *strongSelf = weakSelf; if (!strongSelf) { return; } @@ -88,7 +92,7 @@ if (strongSelf.backgroundTaskId == UIBackgroundTaskInvalid) { return; } - DDLogInfo(@"%@ %@ background task expired", strongSelf.logTag, strongSelf.label); + DDLogInfo(@"%@ %@ background task expired.", strongSelf.logTag, strongSelf.label); strongSelf.backgroundTaskId = UIBackgroundTaskInvalid; if (strongSelf.completionBlock) { @@ -100,6 +104,11 @@ // If a background task could not be begun, call the completion block. if (self.backgroundTaskId == UIBackgroundTaskInvalid) { + + DDLogInfo(@"%@ %@ background task could not be started.", self.logTag, self.label); + + // Make a local copy of completionBlock to ensure that it is called + // exactly once. BackgroundTaskCompletionBlock _Nullable completionBlock; @synchronized(self) { @@ -107,7 +116,7 @@ self.completionBlock = nil; } if (completionBlock) { - dispatch_async(dispatch_get_main_queue(), ^{ + DispatchMainThreadSafe(^{ completionBlock(BackgroundTaskState_CouldNotStart); }); } @@ -119,22 +128,24 @@ { // Make a local copy of this state, since this method is called by `dealloc`. UIBackgroundTaskIdentifier backgroundTaskId; + BackgroundTaskCompletionBlock _Nullable completionBlock; NSString *logTag = self.logTag; NSString *label = self.label; - BackgroundTaskCompletionBlock _Nullable completionBlock = self.completionBlock; @synchronized(self) { backgroundTaskId = self.backgroundTaskId; + completionBlock = self.completionBlock; } if (backgroundTaskId == UIBackgroundTaskInvalid) { + OWSAssert(!completionBlock); return; } // endBackgroundTask must be called on the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ - DDLogInfo(@"%@ %@ background task completed", logTag, label); + DispatchMainThreadSafe(^{ + DDLogVerbose(@"%@ %@ background task completed.", logTag, label); [CurrentAppContext() endBackgroundTask:backgroundTaskId]; if (completionBlock) {