session-ios/SignalServiceKit/src/Util/OWSBackgroundTask.m
2018-02-09 10:31:29 -05:00

171 lines
5.2 KiB
Objective-C

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackgroundTask.h"
#import "AppContext.h"
#import "Threading.h"
@interface OWSBackgroundTask ()
@property (nonatomic, readonly) NSString *label;
// 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
#pragma mark -
@implementation OWSBackgroundTask
+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr
{
OWSAssert(labelStr);
NSString *label = [NSString stringWithFormat:@"%s", labelStr];
return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil];
}
+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr
completionBlock:(BackgroundTaskCompletionBlock)completionBlock
{
OWSAssert(labelStr);
NSString *label = [NSString stringWithFormat:@"%s", labelStr];
return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock];
}
+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label
{
return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil];
}
+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label
completionBlock:(BackgroundTaskCompletionBlock)completionBlock
{
return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock];
}
- (instancetype)initWithLabel:(NSString *)label completionBlock:(BackgroundTaskCompletionBlock _Nullable)completionBlock
{
self = [super init];
if (!self) {
return self;
}
OWSAssert(label.length > 0);
_label = label;
self.completionBlock = completionBlock;
[self startBackgroundTask];
return self;
}
- (void)dealloc
{
[self endBackgroundTask];
}
- (void)startBackgroundTask
{
// beginBackgroundTaskWithExpirationHandler must be called on the main thread.
__weak typeof(self) weakSelf = self;
self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{
// Supposedly [UIApplication beginBackgroundTaskWithExpirationHandler]'s handler
// will always be called on the main thread, but in practice we've observed
// otherwise. We use DispatchSyncMainThreadSafe() (note the sync) to ensure that
// this work is done on the main thread.
//
// See: https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio)
//
// Note the usage of OWSCAssert() to avoid capturing a reference to self.
OWSCAssert([NSThread isMainThread]);
DispatchSyncMainThreadSafe(^{
OWSBackgroundTask *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Make a local copy of completionBlock to ensure that it is called
// exactly once.
BackgroundTaskCompletionBlock _Nullable completionBlock = nil;
@synchronized(strongSelf)
{
if (strongSelf.backgroundTaskId == UIBackgroundTaskInvalid) {
return;
}
DDLogInfo(@"%@ %@ background task expired.", strongSelf.logTag, strongSelf.label);
strongSelf.backgroundTaskId = UIBackgroundTaskInvalid;
completionBlock = strongSelf.completionBlock;
strongSelf.completionBlock = nil;
}
if (completionBlock) {
completionBlock(BackgroundTaskState_Expired);
}
});
}];
// 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)
{
completionBlock = self.completionBlock;
self.completionBlock = nil;
}
if (completionBlock) {
completionBlock(BackgroundTaskState_CouldNotStart);
}
}
}
- (void)endBackgroundTask
{
// Make a local copy of this state, since this method is called by `dealloc`.
UIBackgroundTaskIdentifier backgroundTaskId;
BackgroundTaskCompletionBlock _Nullable completionBlock;
@synchronized(self)
{
backgroundTaskId = self.backgroundTaskId;
completionBlock = self.completionBlock;
self.completionBlock = nil;
}
if (backgroundTaskId == UIBackgroundTaskInvalid) {
OWSAssert(!completionBlock);
return;
}
// endBackgroundTask must be called on the main thread.
DispatchMainThreadSafe(^{
if (completionBlock) {
completionBlock(BackgroundTaskState_Success);
}
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[CurrentAppContext() endBackgroundTask:backgroundTaskId];
}
});
}
@end