Improve background task logic.

This commit is contained in:
Matthew Chen 2017-12-15 13:52:39 -05:00
parent f9ce34f553
commit c3b6de4f83
2 changed files with 31 additions and 9 deletions

View file

@ -10,6 +10,17 @@ typedef NS_ENUM(NSUInteger, BackgroundTaskState) {
typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState 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 @interface OWSBackgroundTask : NSObject
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;

View file

@ -4,6 +4,7 @@
#import "OWSBackgroundTask.h" #import "OWSBackgroundTask.h"
#import "AppContext.h" #import "AppContext.h"
#import "Threading.h"
@interface OWSBackgroundTask () @interface OWSBackgroundTask ()
@ -12,6 +13,7 @@
// This property should only be accessed while synchronized on this instance. // This property should only be accessed while synchronized on this instance.
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; @property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
// This property should only be accessed while synchronized on this instance.
@property (nonatomic, nullable) BackgroundTaskCompletionBlock completionBlock; @property (nonatomic, nullable) BackgroundTaskCompletionBlock completionBlock;
@end @end
@ -74,12 +76,14 @@
- (void)startBackgroundTask - (void)startBackgroundTask
{ {
__weak typeof(self) weakSelf = self;
// beginBackgroundTaskWithExpirationHandler must be called on the main thread. // beginBackgroundTaskWithExpirationHandler must be called on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ DispatchMainThreadSafe(^{
__weak typeof(self) weakSelf = self;
self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{
OWSAssert([NSThread isMainThread]); // Note the usage of OWSCAssert() to avoid capturing a reference to self.
__strong typeof(self) strongSelf = weakSelf; OWSCAssert([NSThread isMainThread]);
OWSBackgroundTask *strongSelf = weakSelf;
if (!strongSelf) { if (!strongSelf) {
return; return;
} }
@ -88,7 +92,7 @@
if (strongSelf.backgroundTaskId == UIBackgroundTaskInvalid) { if (strongSelf.backgroundTaskId == UIBackgroundTaskInvalid) {
return; return;
} }
DDLogInfo(@"%@ %@ background task expired", strongSelf.logTag, strongSelf.label); DDLogInfo(@"%@ %@ background task expired.", strongSelf.logTag, strongSelf.label);
strongSelf.backgroundTaskId = UIBackgroundTaskInvalid; strongSelf.backgroundTaskId = UIBackgroundTaskInvalid;
if (strongSelf.completionBlock) { if (strongSelf.completionBlock) {
@ -100,6 +104,11 @@
// If a background task could not be begun, call the completion block. // If a background task could not be begun, call the completion block.
if (self.backgroundTaskId == UIBackgroundTaskInvalid) { 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; BackgroundTaskCompletionBlock _Nullable completionBlock;
@synchronized(self) @synchronized(self)
{ {
@ -107,7 +116,7 @@
self.completionBlock = nil; self.completionBlock = nil;
} }
if (completionBlock) { if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{ DispatchMainThreadSafe(^{
completionBlock(BackgroundTaskState_CouldNotStart); completionBlock(BackgroundTaskState_CouldNotStart);
}); });
} }
@ -119,22 +128,24 @@
{ {
// Make a local copy of this state, since this method is called by `dealloc`. // Make a local copy of this state, since this method is called by `dealloc`.
UIBackgroundTaskIdentifier backgroundTaskId; UIBackgroundTaskIdentifier backgroundTaskId;
BackgroundTaskCompletionBlock _Nullable completionBlock;
NSString *logTag = self.logTag; NSString *logTag = self.logTag;
NSString *label = self.label; NSString *label = self.label;
BackgroundTaskCompletionBlock _Nullable completionBlock = self.completionBlock;
@synchronized(self) @synchronized(self)
{ {
backgroundTaskId = self.backgroundTaskId; backgroundTaskId = self.backgroundTaskId;
completionBlock = self.completionBlock;
} }
if (backgroundTaskId == UIBackgroundTaskInvalid) { if (backgroundTaskId == UIBackgroundTaskInvalid) {
OWSAssert(!completionBlock);
return; return;
} }
// endBackgroundTask must be called on the main thread. // endBackgroundTask must be called on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ DispatchMainThreadSafe(^{
DDLogInfo(@"%@ %@ background task completed", logTag, label); DDLogVerbose(@"%@ %@ background task completed.", logTag, label);
[CurrentAppContext() endBackgroundTask:backgroundTaskId]; [CurrentAppContext() endBackgroundTask:backgroundTaskId];
if (completionBlock) { if (completionBlock) {