I found that sometimes you may be doing something in your iPhone application that requires the user to wait while it completes. Often this is a network related activity, but in other cases it may not be. In my case I was parsing the response from a network connection and wanted the network activity indicator to keep spinning even though it had already downloaded the content.
below is what i'm doing:
applicationDelegate.m :
- (void)setNetworkActivityIndicatorVisible:(BOOL)setVisible
{
static NSInteger NumberOfCallsToSetVisible = 0;
if (setVisible)
NumberOfCallsToSetVisible++;
else
NumberOfCallsToSetVisible--;
// Display the indicator as long as our static counter is > 0.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(NumberOfCallsToSetVisible > 0)];
}
otherView.m:
dispatch_queue_t dataLoadingQueue = dispatch_queue_create("synchronise", NULL);
dispatch_async(dataLoadingQueue,
^{
[appDelegate setNetworkActivityIndicatorVisible:YES];
[[DataLoader instance]LoadDataForGrewal];
[[FieldConsultantViewModelManager instance] resetCache];
[[DailyFieldConsultantViewModelManager instance] clearCache];
[appDelegate loadMainViews];
[[DataLoader instance]LoadDataForOtherEntities];
[appDelegate setNetworkActivityIndicatorVisible:NO];
});
dispatch_release(dataLoadingQueue);
as u can see above, i'm trying to keep the network indicator while updating the data into the database but it does not work , any clue / suggestions ?
Thanks
EDIT :
dispatch_queue_t dataLoadingQueue = dispatch_queue_create("synchronise", NULL);
dispatch_async(dataLoadingQueue,
^{
dispatch_async(dispatch_get_main_queue(), ^{ [self setNetworkActivityIndicatorVisible:YES]; });
[[DataLoader instance]LoadDataForGrewal];
[[FieldConsultantViewModelManager instance] resetCache];
[[DailyFieldConsultantViewModelManager instance] clearCache];
[appDelegate loadMainViews];
[[DataLoader instance]LoadDataForOtherEntities];
dispatch_async(dispatch_get_main_queue(), ^{ [self setNetworkActivityIndicatorVisible:NO]; });
});
dispatch_release(dataLoadingQueue);
it does not work i'm not sure why because i'm newbie in ios
try to dispatch your setNetworkActivityIndicatorVisible: call on main queue, because UIApplication is in UIKit and UIKit is not thread-safe.
For anyone looking for a complete solution, this is what I use (in Swift). It delays a short duration when stopping the network indicator. This will prevent the indicator from flickering if you have a lot of serial network request.
private var networkActivityCount = 0
func updateNetworkActivityCount(increaseNumber increase:Bool)
{
let appendingCount = increase ? 1 : -1
networkActivityCount += appendingCount
let delayInSeconds = increase ? 0.0 : 0.7
perform({ () -> () in
let application = UIApplication.sharedApplication()
let shouldBeVisible = self.networkActivityCount > 0
if application.networkActivityIndicatorVisible != shouldBeVisible {
application.networkActivityIndicatorVisible = shouldBeVisible
}
}, afterDelay: delayInSeconds)
}
Where perform:afterDelay is defined as
public func perform(block: ()->(), afterDelay:Double) {
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(afterDelay * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
block()
})
}
Just call updateNetworkActivityCount(increaseNumber: true) before your request and the same with false in your request callback. I.e:
updateNetworkActivityCount(increaseNumber: true)
let task = urlSession.dataTaskWithRequest(request) { data, response, error in
self.updateNetworkActivityCount(increaseNumber: false)
...
Related
Is there a good way to call an external method after a set time limit for completing the long process outlined below? I would like the long process to stop trying after a set interval and call a method to try something else and wrap up the request.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//// LONG PROCESS
dispatch_async(dispatch_get_main_queue(), ^{
//// RESULTS PROCESS
});
});
In order to "kill" the process that's running your block, you'll have to check a condition. This will allow you to do cleanup. Consider the following modifications:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
BOOL finished = NO;
__block BOOL cancelled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (!finished) {
cancelled = YES;
}
});
void (^cleanup)() = ^{
// CLEANUP
};
//// LONG PROCESS PORTION #1
if (cancelled) {
cleanup();
return;
}
//// LONG PROCESS PORTION #2
if (cancelled) {
cleanup();
return;
}
// etc.
finished = YES;
dispatch_async(dispatch_get_main_queue(), ^{
//// RESULTS PROCESS
});
});
In the ////Long Process change a boolean value (like BOOL finished) to true when finished.
After the call to dispatch_async(...) you typed here, add this:
int64_t delay = 20.0; // In seconds
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^(void){
if (!finished) {
//// Stop process and timeout
}
});
In this way, after 20 seconds (or any time you want) you can check if the process is still loading and take some provisions.
I've used this method for Swift:
let delay = 0.33 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
//// RESULTS PROCESS
}
I'm working on the chat app, in chat screen my app need to check new message every 5s. I want to do that in background thread so my app will be not blocked UI. I tried the code below but when user typing the message, the UI seems to be blocked. Also, I cannot fire this task when user exit the chat screen.
This is my code I tried:
getLatestMessagesWithInterval() is called in viewwillAppear()
-(void) getLatestMessagesWithInterval
{
NSLog(#"GET MESSAGE INTERVAL");
[self retrieveLatestChatMessages];
// Call this method again using GCD
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self getLatestMessagesWithInterval];
});
}
-(void) retrieveLatestChatMessages
{
if([[NSUserDefaults standardUserDefaults] boolForKey:#"LoggedIn"]) {
NSDictionary * userDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"SessionDictionary"];
.....
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[LSDataManager sharedDataManager] getLatestMessagesWithAuthKey:authenKey andLimit:limit withBlock:^ (NSDictionary* responseDict)
{
if (responseDict) {
[self loadDataFromServer:responseDict];
NSArray* lastMessageArray= nil;
//filter message data
if (self.MessagesArray.count >0) {
//perform data
self.TempdataSource = [[[ContentManager sharedManager] generateConversation:lastMessageArray withSenderID:self.senderID] mutableCopy];
//compare 2 arrays
if ([self.TempdataSource count] == [self.dataSource count]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
self.dataSource = [self.TempdataSource mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
[self refreshMessages];
});
}
}
}
}
}];
});
}
}
I have called retrieveLatestChatMessages() to get message from server. the server will return in block. After performing the data, I reload tableview in main thread. Pls. help me to correct it. Thanks in advance.
Communicate with API asynchronously for example use AFNetworking and your UI will not be blocked, also you can set timer for every second and call something like that
if (numberOfSeconds % 5 == 0) {
numberOfSeconds = 0;
retrieveLatestChatMessages()
}
Two options
Option 1
Call that method every 5 seconds using NSTimer.
I would recommend not to use it.
Option 2
Have an app TCP based for chat messages.
This would be best option for chat messages.
Edit 1
learn how to use TCP as per your language. For iOS TCP follow link
http://www.tekritisoftware.com/sites/default/files/Socket_Programing_for_IOS.pdf
I am looking for a small scenario that how can we trace the "dispatch_async" is running or not?.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
//back ground process
});
In my case, my app will be in foreground I started the back ground thread and when I bring app from background to foreground I need to check whether it is still running or not. I should not call the same process if it is still running. any idea?
The easiest way to do this (without keeping a reference to every dispatch or a flag for entering/leaving asynchronous tasks) is by using dispatch_group notifications. See the example link and code below:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 2
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
}
Note how:
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
will not be called until after
dispatch_group_leave(downloadGroup); // 3
is called.
You should setup your threading to where you can work with callbacks like this to determine states. You should try to avoid using boolean flags at all costs, as this is exactly what dispatch groups are for. It's also hard to keep track of numerous asynchronous calls using boolean states.
link: dispatch groups
The question is wrong - dispatch_async is running while you call it and stops running when the call returns, which is practically immediately. What you really want to know is whether the dispatched block is running or not. The simplest way is something along the lines of
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self blockIsRunning:YES];
// do stuff
[self blockIsRunning:NO];
});
or if you want to know whether the block has run once, you would do something like
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self blockStarted];
// do stuff
[self blockFinished];
});
Alternatively, use NSOperationQueue and a subclass of NSOperation so instead of an anonymous block you have a proper object that you can ask whether it is ready, cancelled, executing, or finished.
I'm trying to do a share operation where I call a function with async block but in my next if statement I need to get the value which is completed in the block to continue. This is my code which will highlight more detail. I heard about NSLock and tried using it but it didnt work, may be I'm doing something lock, I'm not much familiar with locks.
-(void) shareOperation
{
__block NSString *resultText;
BOOL continueSharing;
NSLock *conditionLock=[[NSLock alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[conditionLock lock];
[self performSomeAsynchronousOperation completionBlock:^(NSError *tError, bool status)
{
dispatch_async(dispatch_get_main_queue(),
^{
if (status)
{
resultText = #"Operation completed. Would you like to continue?";
}
else
{
resultText = #" Operation failed. Would you still like to continue?";
}
UIAlertView *result = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:[NSArray arrayWithObjects:#"OK",nil] onDismiss:^(int buttonIndex)
{
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0)
{
continueSharing=YES;
[conditionLock unlock];
NSLog(#"We are continuing sharing :)");
}
}onCancel:^{
continueSharing=NO;
[conditionLock unlock];
NSLog(#"cancelled");
}]; [result show];
});
}];
});
}
//should continue only after earlier if block finishes, ie after getting the continueSharing value
if (continueSharing)
{
[self performSomeAnotherAsynchronousOperation];
}
}
Rather than using locks (which are only designed to ensure that there is not simultaneous access to some shared resource), you could use semaphores (which are designed so that one thread can wait for a signal from another thread, which is needed here).
So, you should create a semaphore:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
The alert view completion blocks would signal that semaphore:
dispatch_semaphore_signal(semaphore);
And where you want to wait for that signal (before performSomeAnotherAsynchronousOperation):
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
I tweaked your code a bit, but most notably, changed it so that it would not block the main queue (which you never want to do) by making sure the dispatch_semaphore_wait is done in a background queue. Also note that the dispatch_semaphore_signal is not inside the if statement. This resulted in:
- (void)shareOperation
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block BOOL continueSharing = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performSomeAsynchronousOperationWithCompletionBlock:^(NSError *tError, bool status){
dispatch_async(dispatch_get_main_queue(),^{
NSString *resultText;
if (status)
resultText = #"Operation completed. Would you like to continue?";
else
resultText = #"Operation failed. Would you still like to continue?";
UIAlertView *alertView = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:#[#"OK"] onDismiss:^(int buttonIndex) {
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0) {
continueSharing = YES;
NSLog(#"We are continuing sharing :)");
}
dispatch_semaphore_signal(semaphore);
} onCancel:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"cancelled");
}];
[alertView show];
});
}];
// should continue only after earlier if block finishes, ie after getting the continueSharing value
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (continueSharing)
[self performSomeAnotherAsynchronousOperation];
});
}
Even better, you should not use any blocking mechanism like semaphores (certainly not on the main queue), but rather one should simply have the completion block for the alert view initiate the next step of the process directly, itself. I understand that you say that this is not practical in your scenario, but it is generally the correct way to handle these scenarios.
You can use the delay block
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
})
I'm a bit confused about how and when to use beginBackgroundTaskWithExpirationHandler.
Apple shows in their examples to use it in applicationDidEnterBackground delegate, to get more time to complete some important task, usually a network transaction.
When looking on my app, it seems like most of my network stuff is important, and when one is started I would like to complete it if the user pressed the home button.
So is it accepted/good practice to wrap every network transaction (and I'm not talking about downloading big chunk of data, it mostly some short xml) with beginBackgroundTaskWithExpirationHandler to be on the safe side?
If you want your network transaction to continue in the background, then you'll need to wrap it in a background task. It's also very important that you call endBackgroundTask when you're finished - otherwise the app will be killed after its allotted time has expired.
Mine tend look something like this:
- (void) doUpdate
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
// Do something with the result
[self endBackgroundUpdateTask];
});
}
- (void) beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
I have a UIBackgroundTaskIdentifier property for each background task
Equivalent code in Swift
func doUpdate () {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = beginBackgroundUpdateTask()
var response: URLResponse?, error: NSError?, request: NSURLRequest?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
// Do something with the result
endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.shared.endBackgroundTask(taskID)
}
The accepted answer is very helpful and should be fine in most cases, however two things bothered me about it:
As a number of people have noted, storing the task identifier as a property means that it can be overwritten if the method is called multiple times, leading to a task that will never be gracefully ended until forced to end by the OS at the time expiration.
This pattern requires a unique property for every call to beginBackgroundTaskWithExpirationHandler which seems cumbersome if you have a larger app with lots of network methods.
To solve these issues, I wrote a singleton that takes care of all the plumbing and tracks active tasks in a dictionary. No properties needed to keep track of task identifiers. Seems to work well. Usage is simplified to:
//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];
//do stuff
//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];
Optionally, if you want to provide a completion block that does something beyond ending the task (which is built in) you can call:
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{
//do stuff
}];
Relevant source code available below (singleton stuff excluded for brevity). Comments/feedback welcome.
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
#synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
#synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
#synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
}
Here is a Swift class that encapsulates running a background task:
class BackgroundTask {
private let application: UIApplication
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask) -> ()) {
// NOTE: The handler must call end() when it is done
let backgroundTask = BackgroundTask(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
self.identifier = application.beginBackgroundTaskWithExpirationHandler {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}
}
The simplest way to use it:
BackgroundTask.run(application) { backgroundTask in
// Do something
backgroundTask.end()
}
If you need to wait for a delegate callback before you end, then use something like this:
class MyClass {
backgroundTask: BackgroundTask?
func doSomething() {
backgroundTask = BackgroundTask(application)
backgroundTask!.begin()
// Do something that waits for callback
}
func callback() {
backgroundTask?.end()
backgroundTask = nil
}
}
As noted here and in answers to other SO questions, you do NOT want to use beginBackgroundTask only just when your app will go into the background; on the contrary, you should use a background task for any time-consuming operation whose completion you want to ensure even if the app does go into the background.
Therefore your code is likely to end up peppered with repetitions of the same boilerplate code for calling beginBackgroundTask and endBackgroundTask coherently. To prevent this repetition, it is certainly reasonable to want to package up the boilerplate into some single encapsulated entity.
I like some of the existing answers for doing that, but I think the best way is to use an Operation subclass:
You can enqueue the Operation onto any OperationQueue and manipulate that queue as you see fit. For example, you are free to cancel prematurely any existing operations on the queue.
If you have more than one thing to do, you can chain multiple background task Operations. Operations support dependencies.
The Operation Queue can (and should) be a background queue; thus, there is no need to worry about performing asynchronous code inside your task, because the Operation is the asynchronous code. (Indeed, it makes no sense to execute another level of asynchronous code inside an Operation, as the Operation would finish before that code could even start. If you needed to do that, you'd use another Operation.)
Here's a possible Operation subclass:
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
It should be obvious how to use this, but in case it isn't, imagine we have a global OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
So for a typical time-consuming batch of code we would say:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
If your time-consuming batch of code can be divided into stages, you might want to bow out early if your task is cancelled. In that case, just return prematurely from the closure. Note that your reference to the task from within the closure needs to be weak or you'll get a retain cycle. Here's an artificial illustration:
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
In case you have cleanup to do in case the background task itself is cancelled prematurely, I've provided an optional cleanup handler property (not used in the preceding examples). Some other answers were criticised for not including that.
I implemented Joel's solution. Here is the complete code:
.h file:
#import <Foundation/Foundation.h>
#interface VMKBackgroundTaskManager : NSObject
+ (id) sharedTasks;
- (NSUInteger)beginTask;
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
- (void)endTaskWithKey:(NSUInteger)_key;
#end
.m file:
#import "VMKBackgroundTaskManager.h"
#interface VMKBackgroundTaskManager()
#property NSUInteger taskKeyCounter;
#property NSMutableDictionary *dictTaskIdentifiers;
#property NSMutableDictionary *dictTaskCompletionBlocks;
#end
#implementation VMKBackgroundTaskManager
+ (id)sharedTasks {
static VMKBackgroundTaskManager *sharedTasks = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedTasks = [[self alloc] init];
});
return sharedTasks;
}
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
#synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
#synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
#synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
NSLog(#"Task ended");
}
}
}
#end