Cue's iOS concurrency library, TheKitchenSync, provides you with a set of advanced locks and thread-safe collections, similar to what you might find in Java.
You can get TheKitchenSync in your project within about 5 minutes: step-by-step installation instructions. Then just #import "TheKitchenSync.h"
and you're ready to roll!
Cue's thread-safe array and dictionary classes support all of the basic operations of arrays and dictionaries, as well as the new subscript operators:
CueSyncArray *syncArray = [@[@"some", @"items"] cueConcurrent];
CueSyncDictionary *syncDict = [@{ @"key" : @"val" } cueConcurrent];
[self cuePerformBlockInBackground:^{
[syncArray insertObject:@"more" atIndex:1];
syncDict[@"key2"] = @"val2";
}];
int i = 0;
// We cannot guarantee safety during normal for-loops, but foreach loops are safe.
for (id obj in syncArray) {
// Since we copy the array before enumerating, you can even mutate mid-loop!
syncArray[i++] = @"I've been mutated!";
}
The CueTaskQueue is similar in concept to a dispatch queue, but with more control. For one, it explicitly maintains a user-configurable number of dedicated threads for execution. In addition, it allows the client to set a delegate to implement custom task deduping logic.
CueTaskQueue *queue = [[CueTaskQueue alloc] initWithName:@"PewPewQueue"];
// relatively low-priority queue.
queue.threadPriority = 0.3;
// single-thread so you don't have to worry about @synchronizing everything.
[queue startWithThreadCount:1];
for (int i = 0; i < 10; ++i) {
[queue addTask:[CueBlockTask taskWithKey:@(i) priority:1.0f executionBlock:^(CueTask *task) {
NSLog(@"Task %d reporting for duty!", i);
}]];
}
// Cleans up and removes the thread.
[queue finish];
Similar to Guava MapMaker, CueLoadingCache and CueLRUCache are thread-safe containers that allow programmatic generation of values by key. Simply pass a loader block to either object, and it will apply that block to every key it is passed:
// This cache object takes a filename as its key, and returns its NSData from disk.
CueLoadingCache *fileLoader = [[CueLoadingCache alloc] initWithLoader:^id(id key) {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docPath = [paths[0] stringByAppendingFormat:@"/%@",path];
return [NSData dataWithContentsOfFile:docPath];
} isMemorySensitive:YES];
// Loads from disk the first time...
NSData *valueFirstTime = fileLoader[@"TextFile1.txt"];
// Loads from cache the second time.
NSData *valueSecondTime = fileLoader[@"TextFile1.txt"];
For more complex locking schemes, TheKitchenSync provides CueFairLock, as well as a CueReadWriteLock, although we will warn you that in benchmarking, CueReadWriteLock proved to be far slower than normal or recursive locks. We are working on improving it, but in the meantime think hard about whether you want to incur this overhead.
Also provided is CueStackLock, which uses stack allocation to guarantee unlocking when execution leaves the current scope. This helps minimize forgotten unlocks and guarantees correct exception cleanup.
- (BOOL)safeLockedQuery {
// Guarantees lock until scope ends.
CueStackLock(_lock);
if ([self needsToBreak]) {
return NO;
}
return [self potentialThrowQuery];
}
Make sure you compile as Objective C++ when using CueStackLock. Generally this means changing your file extension from .m to .mm
We know there is a lot more that can be done to build great libraries, and concurrency is hard!
We're always happy to receive pull requests! Here are some potential improvements:
- In
-[CueLRUCache loaderForKey]
, it's possible to improve the locking performance on reads by getting more granular with the OrderedDictionary implementation, and perhaps writing a LinkedList implementation instead of using NSMutableArray. - If anybody wants to abstract the common code out between CueLoadingCache and CueLRUCache, we'd be eternally grateful!
Copyright 2013 TheKitchenSync Authors.
Published under The Apache License, see LICENSE