Merge branch 'charlesmchen/attachmentDownloadsVsBackground'

pull/1/head
Matthew Chen 8 years ago
commit 40ec869072

@ -445,7 +445,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
DDLogWarn(@"%@ applicationWillResignActive.", self.logTag); DDLogWarn(@"%@ applicationWillResignActive.", self.logTag);
UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:nil]; __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([TSAccountManager isRegistered]) { if ([TSAccountManager isRegistered]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@ -454,8 +455,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[self showScreenProtection]; [self showScreenProtection];
} }
[SignalApp.sharedApp.homeViewController updateInboxCountLabel]; [SignalApp.sharedApp.homeViewController updateInboxCountLabel];
[application endBackgroundTask:bgTask];
backgroundTask = nil;
}); });
} else {
backgroundTask = nil;
} }
}); });

@ -73,6 +73,7 @@
#import <SignalServiceKit/OWSAnalytics.h> #import <SignalServiceKit/OWSAnalytics.h>
#import <SignalServiceKit/OWSAnalyticsEvents.h> #import <SignalServiceKit/OWSAnalyticsEvents.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h> #import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSBackgroundTask.h>
#import <SignalServiceKit/OWSCallAnswerMessage.h> #import <SignalServiceKit/OWSCallAnswerMessage.h>
#import <SignalServiceKit/OWSCallBusyMessage.h> #import <SignalServiceKit/OWSCallBusyMessage.h>
#import <SignalServiceKit/OWSCallHangupMessage.h> #import <SignalServiceKit/OWSCallHangupMessage.h>

@ -584,16 +584,19 @@ protocol CallServiceObserver: class {
self.call = newCall self.call = newCall
let backgroundTask = UIApplication.shared.beginBackgroundTask { var backgroundTask = OWSBackgroundTask(label:"\(#function)", completionBlock: { [weak self] _ in
AssertIsOnMainThread()
guard let strongSelf = self else {
return
}
let timeout = CallError.timeout(description: "background task time ran out before call connected.") let timeout = CallError.timeout(description: "background task time ran out before call connected.")
DispatchQueue.main.async {
guard self.call == newCall else { guard strongSelf.call == newCall else {
Logger.warn("\(self.logTag) ignoring obsolete call in \(#function)") Logger.warn("\(strongSelf.logTag) ignoring obsolete call in \(#function)")
return return
}
self.handleFailedCall(failedCall: newCall, error: timeout)
} }
} strongSelf.handleFailedCall(failedCall: newCall, error: timeout)
})
let incomingCallPromise = firstly { let incomingCallPromise = firstly {
return getIceServers() return getIceServers()
@ -674,7 +677,8 @@ protocol CallServiceObserver: class {
} }
}.always { }.always {
Logger.debug("\(self.logTag) ending background task awaiting inbound call connection") Logger.debug("\(self.logTag) ending background task awaiting inbound call connection")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = nil
} }
incomingCallPromise.retainUntilComplete() incomingCallPromise.retainUntilComplete()
} }

@ -3,9 +3,11 @@
// //
#import "OWSAttachmentsProcessor.h" #import "OWSAttachmentsProcessor.h"
#import "AppContext.h"
#import "Cryptography.h" #import "Cryptography.h"
#import "MIMETypeUtil.h" #import "MIMETypeUtil.h"
#import "NSNotificationCenter+OWS.h" #import "NSNotificationCenter+OWS.h"
#import "OWSBackgroundTask.h"
#import "OWSError.h" #import "OWSError.h"
#import "OWSSignalServiceProtos.pb.h" #import "OWSSignalServiceProtos.pb.h"
#import "TSAttachmentPointer.h" #import "TSAttachmentPointer.h"
@ -155,6 +157,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
{ {
OWSAssert(transaction); OWSAssert(transaction);
__block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[self setAttachment:attachment isDownloadingInMessage:message transaction:transaction]; [self setAttachment:attachment isDownloadingInMessage:message transaction:transaction];
void (^markAndHandleFailure)(NSError *) = ^(NSError *error) { void (^markAndHandleFailure)(NSError *) = ^(NSError *error) {
@ -162,6 +166,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self setAttachment:attachment didFailInMessage:message error:error]; [self setAttachment:attachment didFailInMessage:message error:error];
failureHandler(error); failureHandler(error);
backgroundTask = nil;
}); });
}; };
@ -172,6 +178,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
if (message) { if (message) {
[message touch]; [message touch];
} }
backgroundTask = nil;
}); });
}; };

@ -5,6 +5,7 @@
#import "OWSBatchMessageProcessor.h" #import "OWSBatchMessageProcessor.h"
#import "AppContext.h" #import "AppContext.h"
#import "NSArray+OWS.h" #import "NSArray+OWS.h"
#import "OWSBackgroundTask.h"
#import "OWSMessageManager.h" #import "OWSMessageManager.h"
#import "OWSQueues.h" #import "OWSQueues.h"
#import "OWSSignalServiceProtos.pb.h" #import "OWSSignalServiceProtos.pb.h"
@ -335,10 +336,14 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
return; return;
} }
OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[self processJobs:jobs]; [self processJobs:jobs];
[self.finder removeJobsWithIds:jobs.uniqueIds]; [self.finder removeJobsWithIds:jobs.uniqueIds];
backgroundTask = nil;
DDLogVerbose(@"%@ completed %zd jobs. %zd jobs left.", DDLogVerbose(@"%@ completed %zd jobs. %zd jobs left.",
self.logTag, self.logTag,
jobs.count, jobs.count,

@ -5,6 +5,7 @@
#import "OWSMessageReceiver.h" #import "OWSMessageReceiver.h"
#import "AppContext.h" #import "AppContext.h"
#import "NSArray+OWS.h" #import "NSArray+OWS.h"
#import "OWSBackgroundTask.h"
#import "OWSBatchMessageProcessor.h" #import "OWSBatchMessageProcessor.h"
#import "OWSMessageDecrypter.h" #import "OWSMessageDecrypter.h"
#import "OWSQueues.h" #import "OWSQueues.h"
@ -308,6 +309,8 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
return; return;
} }
__block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[self processJob:job [self processJob:job
completion:^(BOOL success) { completion:^(BOOL success) {
[self.finder removeJobWithId:job.uniqueId]; [self.finder removeJobWithId:job.uniqueId];
@ -316,6 +319,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
success ? @"decrypted" : @"failed to decrypt", success ? @"decrypted" : @"failed to decrypt",
(unsigned long)[OWSMessageDecryptJob numberOfKeysInCollection]); (unsigned long)[OWSMessageDecryptJob numberOfKeysInCollection]);
[self drainQueueWorkStep]; [self drainQueueWorkStep];
backgroundTask = nil;
}]; }];
} }

@ -7,6 +7,7 @@
#import "ContactsUpdater.h" #import "ContactsUpdater.h"
#import "NSData+keyVersionByte.h" #import "NSData+keyVersionByte.h"
#import "NSData+messagePadding.h" #import "NSData+messagePadding.h"
#import "OWSBackgroundTask.h"
#import "OWSBlockingManager.h" #import "OWSBlockingManager.h"
#import "OWSDevice.h" #import "OWSDevice.h"
#import "OWSDisappearingMessagesJob.h" #import "OWSDisappearingMessagesJob.h"
@ -122,11 +123,6 @@ static void *kNSError_MessageSender_IsFatal = &kNSError_MessageSender_IsFatal;
success:(void (^)(void))successHandler success:(void (^)(void))successHandler
failure:(void (^)(NSError *_Nonnull error))failureHandler NS_DESIGNATED_INITIALIZER; failure:(void (^)(NSError *_Nonnull error))failureHandler NS_DESIGNATED_INITIALIZER;
#pragma mark - background task mgmt
- (void)startBackgroundTask;
- (void)endBackgroundTask;
@end @end
#pragma mark - #pragma mark -
@ -159,7 +155,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
@property (nonatomic, readonly) void (^successHandler)(void); @property (nonatomic, readonly) void (^successHandler)(void);
@property (nonatomic, readonly) void (^failureHandler)(NSError *_Nonnull error); @property (nonatomic, readonly) void (^failureHandler)(NSError *_Nonnull error);
@property (nonatomic) OWSSendMessageOperationState operationState; @property (nonatomic) OWSSendMessageOperationState operationState;
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; @property (nonatomic) OWSBackgroundTask *backgroundTask;
@end @end
@ -178,7 +174,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
} }
_operationState = OWSSendMessageOperationStateNew; _operationState = OWSSendMessageOperationStateNew;
_backgroundTaskIdentifier = UIBackgroundTaskInvalid; self.backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
_message = message; _message = message;
_messageSender = messageSender; _messageSender = messageSender;
@ -216,38 +212,6 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
return self; return self;
} }
#pragma mark - background task mgmt
// We want to make sure to finish sending any in-flight messages when the app is backgrounded.
// We have to call `startBackgroundTask` *before* the task is enqueued, since we can't guarantee when the operation will
// be dequeued.
- (void)startBackgroundTask
{
AssertIsOnMainThread();
OWSAssert(self.backgroundTaskIdentifier == UIBackgroundTaskInvalid);
self.backgroundTaskIdentifier = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{
DDLogWarn(@"%@ Timed out while in background trying to send message: %@", self.logTag, self.message);
[self endBackgroundTask];
}];
}
- (void)endBackgroundTask
{
[CurrentAppContext() endBackgroundTask:self.backgroundTaskIdentifier];
}
- (void)setBackgroundTaskIdentifier:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier
{
AssertIsOnMainThread();
// Should only be sent once per operation
OWSAssert(!CurrentAppContext().isMainApp || _backgroundTaskIdentifier == UIBackgroundTaskInvalid);
OWSAssert(!CurrentAppContext().isMainApp || backgroundTaskIdentifier != UIBackgroundTaskInvalid);
_backgroundTaskIdentifier = backgroundTaskIdentifier;
}
#pragma mark - NSOperation overrides #pragma mark - NSOperation overrides
- (BOOL)isExecuting - (BOOL)isExecuting
@ -262,11 +226,6 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
- (void)start - (void)start
{ {
// Should call `startBackgroundTask` before enqueuing the operation
// to ensure we don't get suspended before the operation completes.
OWSAssert(!CurrentAppContext().isMainApp || self.backgroundTaskIdentifier != UIBackgroundTaskInvalid);
[self willChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; [self willChangeValueForKey:OWSSendMessageOperationKeyIsExecuting];
self.operationState = OWSSendMessageOperationStateExecuting; self.operationState = OWSSendMessageOperationStateExecuting;
[self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting];
@ -339,8 +298,6 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
[self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting];
[self didChangeValueForKey:OWSSendMessageOperationKeyIsFinished]; [self didChangeValueForKey:OWSSendMessageOperationKeyIsFinished];
[self endBackgroundTask];
} }
@end @end
@ -452,17 +409,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
success:successHandler success:successHandler
failure:failureHandler]; failure:failureHandler];
// startBackgroundTask must be called on the main thread. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{ NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
// We call `startBackgroundTask` here to prevent our app from suspending while being backgrounded [sendingQueue addOperation:sendMessageOperation];
// until the operation is completed - at which point the OWSSendMessageOperation ends it's background task.
[sendMessageOperation startBackgroundTask];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
[sendingQueue addOperation:sendMessageOperation];
});
}); });
}); });
} }

@ -7,6 +7,7 @@
#import "Cryptography.h" #import "Cryptography.h"
#import "NSNotificationCenter+OWS.h" #import "NSNotificationCenter+OWS.h"
#import "NSTimer+OWS.h" #import "NSTimer+OWS.h"
#import "OWSBackgroundTask.h"
#import "OWSMessageManager.h" #import "OWSMessageManager.h"
#import "OWSMessageReceiver.h" #import "OWSMessageReceiver.h"
#import "OWSSignalService.h" #import "OWSSignalService.h"
@ -72,7 +73,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
@property (nonatomic) NSTimer *backgroundKeepAliveTimer; @property (nonatomic) NSTimer *backgroundKeepAliveTimer;
// This is used to manage the iOS "background task" used to // This is used to manage the iOS "background task" used to
// keep the app alive in the background. // keep the app alive in the background.
@property (nonatomic) UIBackgroundTaskIdentifier fetchingTaskIdentifier; @property (nonatomic) OWSBackgroundTask *backgroundTask;
// We cache this value instead of consulting [UIApplication sharedApplication].applicationState, // We cache this value instead of consulting [UIApplication sharedApplication].applicationState,
// because UIKit only provides a "will resign active" notification, not a "did resign active" // because UIKit only provides a "will resign active" notification, not a "did resign active"
@ -100,7 +101,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
_signalService = [OWSSignalService sharedInstance]; _signalService = [OWSSignalService sharedInstance];
_messageReceiver = [OWSMessageReceiver sharedInstance]; _messageReceiver = [OWSMessageReceiver sharedInstance];
_state = SocketManagerStateClosed; _state = SocketManagerStateClosed;
_fetchingTaskIdentifier = UIBackgroundTaskInvalid;
OWSSingletonAssert(); OWSSingletonAssert();
@ -381,6 +381,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
if ([message.path isEqualToString:@"/api/v1/message"] && [message.verb isEqualToString:@"PUT"]) { if ([message.path isEqualToString:@"/api/v1/message"] && [message.verb isEqualToString:@"PUT"]) {
__block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *decryptedPayload = NSData *decryptedPayload =
@ -389,6 +391,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
if (!decryptedPayload) { if (!decryptedPayload) {
DDLogWarn(@"%@ Failed to decrypt incoming payload or bad HMAC", self.logTag); DDLogWarn(@"%@ Failed to decrypt incoming payload or bad HMAC", self.logTag);
[self sendWebSocketMessageAcknowledgement:message]; [self sendWebSocketMessageAcknowledgement:message];
backgroundTask = nil;
return; return;
} }
@ -398,6 +401,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self sendWebSocketMessageAcknowledgement:message]; [self sendWebSocketMessageAcknowledgement:message];
backgroundTask = nil;
}); });
}); });
} else { } else {
@ -513,7 +517,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
return YES; return YES;
} else if (self.backgroundKeepAliveUntilDate && [self.backgroundKeepAliveUntilDate timeIntervalSinceNow] > 0.f) { } else if (self.backgroundKeepAliveUntilDate && [self.backgroundKeepAliveUntilDate timeIntervalSinceNow] > 0.f) {
OWSAssert(self.backgroundKeepAliveTimer); OWSAssert(self.backgroundKeepAliveTimer);
OWSAssert(self.fetchingTaskIdentifier != UIBackgroundTaskInvalid);
// If app is doing any work in the background, keep web socket alive. // If app is doing any work in the background, keep web socket alive.
return YES; return YES;
} else { } else {
@ -532,7 +535,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
} else if (!self.backgroundKeepAliveUntilDate) { } else if (!self.backgroundKeepAliveUntilDate) {
OWSAssert(!self.backgroundKeepAliveUntilDate); OWSAssert(!self.backgroundKeepAliveUntilDate);
OWSAssert(!self.backgroundKeepAliveTimer); OWSAssert(!self.backgroundKeepAliveTimer);
OWSAssert(self.fetchingTaskIdentifier == UIBackgroundTaskInvalid);
DDLogInfo(@"%s activating socket in the background", __PRETTY_FUNCTION__); DDLogInfo(@"%s activating socket in the background", __PRETTY_FUNCTION__);
@ -551,19 +553,25 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
// Additionally, we want the reconnect timer to work in the background too. // Additionally, we want the reconnect timer to work in the background too.
[[NSRunLoop mainRunLoop] addTimer:self.backgroundKeepAliveTimer forMode:NSDefaultRunLoopMode]; [[NSRunLoop mainRunLoop] addTimer:self.backgroundKeepAliveTimer forMode:NSDefaultRunLoopMode];
self.fetchingTaskIdentifier = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ __weak typeof(self) weakSelf = self;
OWSAssert([NSThread isMainThread]); self.backgroundTask =
[OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__
DDLogInfo(@"%s background task expired", __PRETTY_FUNCTION__); completionBlock:^(BackgroundTaskState backgroundTaskState) {
OWSAssert([NSThread isMainThread]);
[self clearBackgroundState]; __strong typeof(self) strongSelf = weakSelf;
[self applyDesiredSocketState]; if (!strongSelf) {
}]; return;
}
if (backgroundTaskState == BackgroundTaskState_Expired) {
[strongSelf clearBackgroundState];
}
[strongSelf applyDesiredSocketState];
}];
} else { } else {
OWSAssert(self.backgroundKeepAliveUntilDate); OWSAssert(self.backgroundKeepAliveUntilDate);
OWSAssert(self.backgroundKeepAliveTimer); OWSAssert(self.backgroundKeepAliveTimer);
OWSAssert([self.backgroundKeepAliveTimer isValid]); OWSAssert([self.backgroundKeepAliveTimer isValid]);
OWSAssert(self.fetchingTaskIdentifier != UIBackgroundTaskInvalid);
if ([self.backgroundKeepAliveUntilDate timeIntervalSinceNow] < durationSeconds) { if ([self.backgroundKeepAliveUntilDate timeIntervalSinceNow] < durationSeconds) {
// Update state used to keep socket alive in background. // Update state used to keep socket alive in background.
@ -623,11 +631,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
self.backgroundKeepAliveUntilDate = nil; self.backgroundKeepAliveUntilDate = nil;
[self.backgroundKeepAliveTimer invalidate]; [self.backgroundKeepAliveTimer invalidate];
self.backgroundKeepAliveTimer = nil; self.backgroundKeepAliveTimer = nil;
self.backgroundTask = nil;
if (self.fetchingTaskIdentifier != UIBackgroundTaskInvalid) {
[CurrentAppContext() endBackgroundTask:self.fetchingTaskIdentifier];
self.fetchingTaskIdentifier = UIBackgroundTaskInvalid;
}
} }
#pragma mark - Reconnect #pragma mark - Reconnect

@ -4,6 +4,7 @@
#import "OWSAnalytics.h" #import "OWSAnalytics.h"
#import "AppContext.h" #import "AppContext.h"
#import "OWSBackgroundTask.h"
#import "OWSQueues.h" #import "OWSQueues.h"
#import "TSStorageManager.h" #import "TSStorageManager.h"
#import <CocoaLumberjack/CocoaLumberjack.h> #import <CocoaLumberjack/CocoaLumberjack.h>
@ -231,22 +232,19 @@ NSString *NSStringForOWSAnalyticsSeverity(OWSAnalyticsSeverity severity)
DDLogDebug(@"%@ submitting: %@", self.logTag, eventKey); DDLogDebug(@"%@ submitting: %@", self.logTag, eventKey);
__block UIBackgroundTaskIdentifier task; __block OWSBackgroundTask *backgroundTask =
task = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__
failureBlock(); completionBlock:^(BackgroundTaskState backgroundTaskState) {
if (backgroundTaskState == BackgroundTaskState_Success) {
[CurrentAppContext() endBackgroundTask:task]; successBlock();
}]; } else {
failureBlock();
}
}];
// Until we integrate with an analytics platform, behave as though all event delivery succeeds. // Until we integrate with an analytics platform, behave as though all event delivery succeeds.
dispatch_async(self.serialQueue, ^{ dispatch_async(self.serialQueue, ^{
BOOL success = YES; backgroundTask = nil;
if (success) {
successBlock();
} else {
failureBlock();
}
[CurrentAppContext() endBackgroundTask:task];
}); });
} }

@ -0,0 +1,43 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
typedef NS_ENUM(NSUInteger, BackgroundTaskState) {
BackgroundTaskState_Success,
BackgroundTaskState_CouldNotStart,
BackgroundTaskState_Expired,
};
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.
// * Retain a strong reference to the OWSBackgroundTask during the "work".
// * Clear all references to the OWSBackgroundTask when the work is done,
// if possible.
@interface OWSBackgroundTask : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr;
// completionBlock will be called exactly once on the main thread.
+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr
completionBlock:(BackgroundTaskCompletionBlock)completionBlock;
+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label;
// completionBlock will be called exactly once on the main thread.
+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label
completionBlock:(BackgroundTaskCompletionBlock)completionBlock;
@end

@ -0,0 +1,164 @@
//
// Copyright (c) 2017 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.
DispatchMainThreadSafe(^{
__weak typeof(self) weakSelf = self;
self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{
// Note the usage of OWSCAssert() to avoid capturing a reference to self.
OWSCAssert([NSThread isMainThread]);
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;
NSString *logTag = self.logTag;
NSString *label = self.label;
@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(^{
DDLogVerbose(@"%@ %@ background task completed.", logTag, label);
if (completionBlock) {
completionBlock(BackgroundTaskState_Success);
}
[CurrentAppContext() endBackgroundTask:backgroundTaskId];
});
}
@end
Loading…
Cancel
Save