There are many ways to dispatch work with Objective-C, from Grand Central Dispatch to NSOperations however, none of these approaches handle persisting work. ZLTaskManager fills this void. With this library we can persist work from app launch to app launch. We can make sure this work is retried again and again until it succeeds (or until we decide to stop retrying it, more on this later). And thanks to this we can start work without adding endless failsafes to make sure that it is done correctly.
##Getting Starting ###Installation ####Cocoapods CocoaPods is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like AFNetworking in your projects. See the "Getting Started" guide for more information.
Podfile
platform :ios, '7.1'
pod 'ZLTaskManager', '~> 0.0'
##Usage
There are four main classes in ZLTaskManager
1: ZLTaskManager
- Responsible for queueing, stopping, cancelling, restarting etc all work.
2: ZLTask
- How we specify and queue work on the TaskManager
3: ZLTaskWorker
- Where the actual work is done. You must provide the custom implementation of your work in a subclass of this.
3: ZLManager
- Responsible for TaskWorkers
. The TaskManager
will ask subclasses of this to create TaskWorkers
based on the task type it is registered for.
###Initializing
ZLTaskManager
is a shared instance and is initialized at the first call. It is recommended to initialize the TaskManager
and register all available ZLManager
subclasses on app launch, for example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ZLManagerSubclass *yourManager = [ZLManagerSubclass new];
[ZLTaskManager sharedInstance] registerManager:yourManager forTaskType:@"your.task.type"];
}
Once a manager is registered for a certain task type the TaskManager
will automatically start any work for that task type that has been queued and not completed.
NOTE A ZLManager
can be registered for more than one task type BUT only one manager can be registered for each task type.
###Queueing work
Work is specified in the form of ZLTasks
which are then queued on the TaskManager
. A ZLTask
is a way for you to specify everything about the task, from the priority, to its internet requirement to the number of times it should be retried if it fails (can be infinite). However, there are two most important pieces of information:
taskType
- This is a string that defines what kind of task it is. It is used by theTaskManager
to determine whatZLManager
andZLTaskWorker
subclass to use for this task.jsonData
- This is a json compatible NSDictionary (only NSNumbers, NSString, NSArray and other NSDictionaries). You use this to pass any information necessary for completing the work. You will program yourZLTaskWorker
subclass to understand this dictionary and pull the information out of it appropriately.
This is how you create and queue work:
// This should be a constant defined somewhere.
NSString *taskType = @"test.task.type";
NSDictionary *jsonData = @{@"urlToFetch":@"www.example.com/fetch", @"urlToPost":@"www.example.com/post", @"someNumber":@1, @"someParameters":@[@"one", @"two",@"three"]}
ZLTask *task = [[ZLTask alloc] initWithTaskType:taskType jsonData:jsonData];
task.requiresInternet = YES;
task.majorPriority = 10000;
[[ZLTaskManager sharedInstance] queueTask:task];
###Executing Work
All work is done inside your subclasses of ZLTaskWorker
. To understand how to implement your TaskWorker
visit this page.
Your TaskWorker
is created by your subclass of ZLManager
that is registered for that task type. You must override the taskWorkerForWorkItem:
method in your Manager
class. Example below:
- (ZLTaskWorker *)taskWorkerForWorkItem:(ZLInternalWorkItem *)workItem
{
//This should be defined as a constant somewhere
NSString *taskType = @"test.task.type";
ZLTaskWorker *taskWorker;
if ([workItem.taskType isEqualToString:taskType]) {
taskWorker = [ZLTaskWorkerSubclass new];
} else if ([workItem.taskType isEqualToString:@"otherTaskTypeManagerHandles"]) {
taskWorker = [ZLOtherTaskWorkerSubclass new];
} else
// If your Manager is registered for a task type you MUST handle it. This line of code should never execute
// if you are implementing this correctly. It is recommended to log here in case this happens so you
// know what's going wrong.
NSLog(@"Manager is not handling task type %@", workItem.taskType);
}
// Required
[taskWorker setupWithWorkItem:workItem];
return taskWorker;
}
###Stopping and backgrounding
In order to keep the iOS from killing our processes while they're running when the application is about to terminate (plus your own app specific needs), we must stop our work. There are two ways to do this, asynchonously or synchronously. Please review this page for more details. You must implement the below code in your applicationWillTerminate
method in your app delegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
// This is a synchronous method.
[[ZLTaskManager sharedInstance] stopAndWaitWithNetworkCancellationBlock:^{
// Cancel any network tasks your task workers may be using so that the cancellation process
// is not waiting on them to finish.
}];
}
####Backgrounding disabled
If you decide to not enable backgrounding then you must stop your work everytime your app enters the background and resume it everytime it enters the for ground, as showed below:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// This is a synchronous method.
[[ZLTaskManager sharedInstance] stopAndWaitWithNetworkCancellationBlock:^{
// Cancel any network tasks your task workers may be using so that the cancellation process
// is not waiting on them to finish.
}];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[ZLTaskManager sharedInstance] resume];
}
####Backgrounding enabled
If you decide to enable backgrounding for ZLTaskManager
in your application then you must start a background task everytime your app enters the background and make sure it starts up again everytime your application becomes active:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
__block UIBackgroundTaskIdentifier backgroundTask;
backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
[[ADTaskManager sharedInstance] stopAndWait];
NSLog(@"ZLTaskManager background tasks ran out of time. Stopping");
[application endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//This is a blocking method. It shouldn't be called anywhere but in this context
[ZLTaskManager waitForTasksToFinishOnSharedInstance];
NSLog(@"ZLTaskManager finished background tasks");
[application endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
});
}
}
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[ZLTaskManager sharedInstance] resume];
}