A Splendid Approach For Automating Background Tasks
I love drop-in modules. I love being able to grow the functionality of an application without having to do much work! But in the suggestions of how to write iPhone apps, I haven’t seen very many ideas about how to write code that allows re-use gracefully.
For example, in one of my apps I need several activities to begin whenever the app is loaded, or becomes active:
1. I want user uploads that did not complete to re-try being sent.
2. I want to check the server for updates, and download new files.
3. I want GPS location data to start being gathered.
4. In-App purchases should be re-attempted if previous calls failed.
5. I have some singleton classes that should be initialized.
6. Some data structures need to be pre-loaded with data, so screens load quickly when the user navigates to them.
And when the app is about to enter the background, there are tasks that need to gracefully end or pause themselves.
As I write new apps, many of them have exactly the same or very similar requirements. Most non-trivial apps will all have those six tasks, as well as others of their own.
For the sake of discussion, I will introduce you to a class I wrote that processes web service calls in a manner that handles interruptions, delays, and lack of connections. This class is called “RobustWebService” (or RWS for short). I will reference it below, to demonstrate this technique. What is important to know about this discussion is that RobustWebService has specific tasks to perform at specific times:
* When a web service call is made, it is added to a pending queue of operations that processes in the background.
* When the app enters the background all calls are canceled, and archived to disk in a “pending” state.
* When the app returns to the foreground all archived web service calls should be re-queued.
If you google around for solutions to having code execute in response to app state changes, you’ll find code examples where you add in calls to your code into your app delegate, because it gets called whenever the application didFinishLaunchingWithOptions, or didBecomeActive, or WillEnterBackground.
But let’s say you start working on another app, which re-uses a lot of your code. Wouldn’t it be nice if you could just copy over some files and get all the functionality, without having to worry about re-wiring up the app delegate methods?
To summarize, we want drop-in modules that, just by the virtue of being in the project, will perform their work, without any additional effort or wiring up. At this point you may be saying “and I also want unicorns and elves to bring me a pizza”. Bear with me.
I’m not sure how I missed this when reading the docs, but when iOS applications change state, it isn’t just the app delegate that knows about it. NSNotifications also get posted for all major state changes. Here is text taken directly from the api reference:
After calling [applicationDidBecomeActive], the application also posts a UIApplicationDidBecomeActiveNotification notification to give interested objects a chance to respond to the transition.
Similarly, there is a UIApplicationDidFinishLaunchingNotification that gets posted immediately after the application finishes launching, and other notifications for entering the background, or entering the foreground.
So this simplifies our code: instead of having to call
[RobustWebService handleAppBecomingActive]
in the app delegate’s implementation of applicationDidBecomeActive, we just have to have RobustWebService respond to UIApplicationDidFinishLaunchingNotification.Now pondering this a moment, you might realize that in order for the RWS class to handle the notification, it has to register as an observer. That call looks something like
[[NSNotificationCenter defaultCenter] addObserver:self
Selector:@selector(handleAppBecomeActive)
name:UIApplicationDidFinishLaunchingNotification
object :nil]
And where can that get done? Remember, we do not want to touch the app delegate because that would defeat the intent of having a self-contained drop-in module. If only there were some way to have the addObserver function call happen automatically for the class. If only…
At this point we have to drop out of “Cocoa” and dig into the underlying technology of Objective-C. Sure enough, there is a class method, called “load” which, if present in your class definition, will automatically be called when the class is first loaded. Let’s restate that in code. If you write this function in any class.m file
+ (void) load
{
// stuff
}
...it will run when the class is loaded by iOS. Interestingly enough, it is run before your app’s main() routine is called so you have to be very careful about what you try to do! Most of your app is NOT actually up and running at this point, but you are guaranteed that all frameworks that your class links too will be loaded first. Frameworks such as NSNotificationCenter, so if you include this in your class.m
+ (void) load
{
[[NSNotificationCenter defaultCenter] addObserver:self
Selector:@selector(handleAppBecomeActive)
name:UIApplicationDidFinishLaunchingNotification
object :nil]
}
Then your handleAppBecomeActive method WILL get called when your app becomes active, without you having to do anything other than include the class.h and class.m in your project.
And if you include this code in your class.m file
+ (void) load;
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAppLaunched) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppResigningActive) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppBecomingActive) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppEnteringBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppEnteringForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppClosing) name:UIApplicationWillTerminateNotification object:nil];
}
Your class will get notified of all app state changes, with no other work required. All you have to do is write the methods you want to be called when specific app state changes occur, and they will be called. This is so cool that it still makes me feel tingly. Enjoy!
Terry