3. Blocks
Blocks are a proposed addition to C.
Like a function pointer, except it also stores the context
the block was created in.
Similar to closures, lambdas, and anonymous functions
in other languages.
4. Blocks
Declaration Syntax:
returnType (^blockName) (Arguments)
Blocks may be anonymous.
Definition Syntax:
^ returnType (Arguments) { code; }
The return type and arguments are optional.
GCD provides function pointer variants to the block APIs.
5. Blocks
Blocks can modify local variables outside of their scope
if the variables have the new __block keyword.
Global and static variables can be modified without the
__block keyword.
Blocks automatically retain Objective-C objects, except
objects that use the __block modifier.
C objects must be manually retained.
Beware of retain cycles.
7. What is GCD?
GCD is a lightweight multithreading engine.
Uses a thread pool.
Developers create queues of blocks rather than
threads.
Uses lock-less exclusion rather than mutual exclusion.
Replaces blocking and polling APIs.
8. Why Multithread on a Single
Core?
Keeps the UI responsive.
UI code runs on the main thread.
Everything else runs on a background thread.
Prevents the main thread from blocking or waiting.
10. Global Queues
Four global queues:
Main, Low, Default, and High.
Only the main thread services the main queue.
The three other queues determine the priority of
background tasks.
Enqueuing is thread safe.
14. dispatch_once
Guaranteed to run only once for the lifetime of the
application.
Fast and thread safe.
Very easy to use.
Great for singletons and static class variables.
15. The Old Way
static MyObject * myObject = nil;
+ myObject {
@synchronized(self) {
if (!myObject) myObject = [MyObject new];
}
return myObject;
}
16. Problems With
@synchronized
@synchronized is slow.
When synchronizing on the class instance, all
other methods that synchronize on it will
temporarily block incoming messages.
You can’t synchronize on the class variable since it
is initially nil.
Using a custom lock also faces the initialization
problem.
17. The GCD Way
static MyObject * myObject = nil;
+ myObject {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!myObject) myObject = [MyObject new];
});
return myObject;
}
19. Mutual Exclusion
Mutual exclusion is classically handled by semaphores
or locks.
Both are most efficient when there is no sharing
conflicts, (i.e. when you don’t need them) and don’t
scale well.
Queues are efficient and scale very well.
In fact, queues are most efficient under high conflict.
20. Thread-safe Access
// myQueue must be a serial queue NSLock * lock = [NSLock new];
dispatch_async(myQueue, ^{ [lock lock];
[self setMySharedVariable:42]; [self setMySharedVariable:42];
}); [lock unlock];
22. Problems with Getters/
Setters
We would rather have the compiler write our getters/
setters.
This technique works well in other methods.
Only immutable objects are fully protected this way.
25. Compare to
@synchronized
[obj accessSharedDataAsync:^(id sharedVar) @synchronized ([obj sharedVar])
{ {
// Critical code here. // Critical code here.
} }
Fast - No Locks Slow - Recursive Lock
Allows private access Must allow public access
Synchronous or Asynchronous Synchronous Only
Can be extended to access many shared Only single-value access.
data values at once.
27. Why Striding?
Myth: My app will go faster if I multithread.
Your app will only be faster if the work is
parallelizable.
Sometimes a block simply contains a loop, and
each iteration can be run independently of the
others.
So, why not spread the iterations across many
threads?
29. dispatch_apply
// myQueue must be a concurrent queue!
dispatch_apply(10, myQueue, ^(size_t idx) {
[self doIndependentTask:idx];
});
30. Problems with
dispatch_apply
One thread per iteration may be overkill and result in
high overhead costs.
Solution: Use striding (i.e. have one thread run many
iterations).
Similar to loop unrolling.
You may need to profile your app to find the ideal
stride length.
31. size_t stride = 4;
size_t iterations = 10;
size_t strideCount = iterations / stride;
// myQueue must be a concurrent queue!
dispatch_apply(strideCount, myQueue, ^(size_t idx) {
size_t i = idx * stride;
size_t stop = i + stride;
do {
[self doIndependentTask:i++];
} while (i < stop);
});
// Pick up any left over iterations.
for (size_t i = strideCount - (strideCount % stride); i < iterations; i++)
[self doIndependentTask:i];
33. What is a Continuation?
Well, it’s complicated...take CS 330.
Think of it like a completion block wrapped in a
completion block wrapped in a completion block...
34. Using Continuations
The following steps are optional, but either 4 or 5 must be
done.
1. Do some processing.
2. Wrap the completion block in another block.
3. Copy the block if it is going to cross threads
(unnecessary with ARC).
4. Pass the completion block to someone else.
5. Execute the completion block.
35. typedef void(^CHCompletion)(NSError * error);
- (void)uploadPhoto:(NSString *)photoPath {
// Do some pre-processing.
[self processPhotoAtPath:photoPath completion:^(NSError * error) {
if (error)
dispatch_async(dispatch_get_main_queue(), ^{
// Inform user of error
});
}];
}
36. - (void)processPhotoAtPath:(NSString *)path completion:
(CHCompletion)completion {
[[completion copy] autorelease];
// Do some resizing and add a caption, then save the modified photo
to a temporary file for memory efficiency.
[self uploadProcessedPhotoAtPath:newPath completion:^(NSError *
error) {
// Delete the temporary file.
if (completion) completion(error);
}];
}