Here's a full example of an application that could support multiple actors changing data while processing networking requests and user interaction.
I've set a guideline for myself to use the BFExecutor linked to the serial dispatch_queue_t when touching anything service related for thread safety.
Pointing out other best practices would be useful, thanks!
#import <Bolts/Bolts.h>
dispatch_queue_t serialQueue;
BFExecutor *serialExecutor;
BFTask *maintask;
int32_t key = 0;
BOOL initialized = FALSE;
@implementation yAppDelegate
- (void)setKey:(int32_t)key_ {
// Don't worry about success, just appending to outstanding tasks
maintask = [maintask continueWithExecutor:serialExecutor withBlock:^id(BFTask *task) {
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
key = key_;
NSLog(@"set key to %d", key);
[source setResult:nil];
if (initialized) {
[self initialize];
}
return source.task;
}];
}
- (BFTask *)downloadConfig {
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
dispatch_async(serialQueue, ^{
NSLog(@"working on init config, key = %d...", key);
// Trigger from a different queue
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[source setResult:nil];
});
});
return source.task;
}
- (BFTask *)initializeService {
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
dispatch_async(serialQueue, ^{
NSLog(@"working on init service, key = %d...", key);
// Trigger from a different queue
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[source setResult:nil];
});
// Set final state
initialized = TRUE;
});
return source.task;
}
- (void)initialize {
int32_t oldKey = key;
__block bool reinit = false;
// Start by chaining it to whatever task is in flight without regard to success
// Everything should use the serialQueue or serialExecutor for thread safety
maintask = [[[maintask continueWithExecutor:serialExecutor withBlock:^id(BFTask *task) {
if (oldKey != key) {
NSLog(@"key out of date (%d != %d). reinitializing...", oldKey, key);
reinit = true;
return [BFTask cancelledTask];
}
return [self downloadConfig];
}] continueWithExecutor:serialExecutor withSuccessBlock:^id(BFTask *task) {
if (oldKey != key) {
NSLog(@"key out of date (%d != %d). reinitializing...", oldKey, key);
reinit = true;
return [BFTask cancelledTask];
}
return [self initializeService];
}] continueWithExecutor:serialExecutor withBlock:^id(BFTask *task) {
if (oldKey != key) {
NSLog(@"key out of date (%d != %d). reinitializing...", oldKey, key);
reinit = true;
}
if (task.error || task.exception || task.isCancelled) {
if (reinit) {
[self initialize];
}
return nil;
} else {
NSLog(@"initService completed = %d", key);
return nil;
}
}];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
serialQueue = dispatch_queue_create("serial", NULL);
serialExecutor = [BFExecutor executorWithDispatchQueue:serialQueue];
// Start with an empty task so all tasks can be strung together without need to initialize
maintask = [BFTask taskWithResult:nil];
// Everything related to changing service state should be contained with the serialQueue dispatch queue
[self setKey:1];
[self initialize];
[self setKey:2];
[self setKey:3];
dispatch_async(dispatch_get_main_queue(), ^(void){
[self setKey:4];
});
dispatch_async(serialQueue, ^(void){
[self setKey:5];
});
[self setKey:6];
// Override point for customization after application launch.
return YES;
}
@end
The results are as expected. Initialization is attempted with the key = 1, but is differed until it stops changing. The service is then initialized on its queue with key = 5 and then re-initialized with key = 4.
Results:
set key to 1
key out of date (0 != 1). reinitializing...
key out of date (0 != 1). reinitializing...
set key to 2
set key to 3
set key to 6
set key to 5
key out of date (1 != 5). reinitializing...
key out of date (1 != 5). reinitializing...
working on init config, key = 5...
working on init service, key = 5...
initService completed = 5
set key to 4
working on init config, key = 4...
working on init service, key = 4...
initService completed = 4