Often when developing applications you want to run some code but only if your application is in a particular state and if the app is not yet in yet in that state, do it later.
One approach would be something like the this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Unfortunately there are some problems with this simple (yet naive) solution.
The most problematic aspect of the simple solution is that we’ve introduced state into our class with the doSomethingWhenActive property. We have to remember when this flag should be turned on or off. We’re also potentially managing the state we’re basing our decisions on (the active/inactive state above).
Every time you introduce state, and behaviour that is dependent on that state, into a class you increase the potential for bugs to creep into the code. Eg, which code paths set and clear that state. Is it possible for the boolean state variable to get stuck on or off because we didn’t reset it in a particular code path, etc.
Another problem with this approach is that if we need to add some more behaviour that is performed when our class is “active” or returns to being active (or some other situation) we may have to add yet another boolean property and yet more conditional logic complicating our class’s behaviour and compounding the likelihood of bugs creeping in.
A more robust solution to this sort of situation is to use a work queue.
Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
In the code above the
performBlockWhenActive: method is private and invocations of
doSomething use it to enqueue work for execution later. You could make this more
general by exposing the
performBlockWhenActive: method publicly. Then clients of that
class can choose whether they want
doSomething to happen immediately or only when
the app is active. Eg:
1 2 3 4 5 6 7 8 9 10
Note: The code above uses an NSOperationQueue instead of a GCD dispatch queue so that tasks that have not been executed can be canceled when the MyClass instance is released. If you want all submitted tasks to execute no matter what, you could use a GCD dispatch queue. Then rather than cancelling all the operations before release you would ensure that the queue was resumed (and thus pending task blocks completed). Be careful with suspending/resuming GCD queues however. They will assert/crash your app if calls to dispatch_resume/dispatch_suspend are unbalanced or if you attempt to release a queue while it is suspended.
Also be careful about introducing retain cycles by making references to self (either implicit or explicit) in the task blocks. This is why I pass the MyClass instance that actions should be performed on into the block as a parameter to help avoid this sort of reference cycle.
So although this solution isn’t without its own potential pitfalls and problems I feel it is a better solution than the first simple attempt.
The queue based solution has the benefits of being more general, requiring less state,
and executing the tasks in the order they are submitted with
I recently employed this pattern in my own code when I needed an application to display a window to the user but only once the app had become the active application.
The call to
performBlockWhenActive: was made in a part of the app’s lifecycle that did
not guarantee that the application had completed launching yet. The same code however
could also be invoked at times when the app was launched and active. By pushing the work
into a queue that was only executing when the app was in the active state I was able to
solve this issue with the minimum amount of fuss and also provide a facility to the
application that could be used in other similar situations.