Share
Embed
Pragmatic Blocks
Blocks are a powerful language feature. However, they are underused and under-appreciated in Objective-C, especially compared to Ruby. My intent is to make blocks easy to understand and show many practical uses of blocks.
Rob Brown
April 12, 2012
Transcript
79 slides
-
Objectives Introduce the concepts behind blocks Touch lightly on Objective-C
syntax Focus on practical applications of blocks -
What are blocks? Pieces of code that can be stored
and executed when needed Also known as lambdas, closures, anonymous functions, first-class functions, higher-order functions Can be passed as a parameter, returned from a method/function, and stored in data structures Blocks make adding new features to a language easy -
What does “closure” mean? Blocks store all the variables that
are in scope when the block is created Closure makes it possible to store a block indefinitely, then restore the context at will Objective-C objects are automatically retained/released C objects must be manually retained/released -
Block Syntax Declaration Syntax: returnType (^blockName) (Arguments) Definition Syntax: ^
returnType (Arguments) { code; } The return type and arguments are optional. -
Block Syntax Example: returnType (^block)(args) = ^(args) { ... };
Block types are ugly, use typedef: typedef returnType (^MyBlock)(args); MyBlock block = ^(args) { ... }; -
Gotchas Retain loops Xcode profiler will warn you of block
retain loops Unlike regular Objective-C objects, blocks are created on the stack Be sure to call -copy if you need it to live beyond the current stack frame Make sure to de-stackify blocks before storing them in a data structure -
What are blocks good for? Completion callbacks Continuations Multi-threading Delegate
replacement Other design pattern modifications -
Callbacks Most blocks are some form of callback Since blocks
have closure, they can restore the caller’s context without any effort on our part -
Completion Blocks Most common use of blocks in Objective-C A
callback that happens only after some task has finished Some operations take an unknown amount of time -
Completion Blocks Object A Object B Block Object A calls
Object B and passes a completion block. -
Object B finishes executing and calls the completion block. Completion
Blocks Object A Object B Block -
Partial Completion Sometimes it may take a long time for
an operation to complete The callee can periodically return partial results The caller accumulates the results The UI appears more responsive -
Continuations Think of it like a completion block wrapped in
a completion block wrapped in a completion block... Essentially building your own runtime stack -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block Object A calls Object B and passes a completion block. So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block Object A and Object B need to know when Object C finishes. So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Object C finishes executing and calls the completion block. Continuations
Object A Object B Object C Block So, Object B wraps Object A’s block in a new completion block. Object B then calls Object C with the new completion block. -
Multithreading: GCD Grand Central Dispatch (GCD) makes heavy use of
blocks Blocks are not tied to a thread until they are executed Blocks can be queued up without blocking threads Threads can spend more time running and less time waiting -
Multithreading: GCD Code can be made asynchronous simply by surrounding
it with dispatch_async(queue, ^{ ... }); -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Multithreading: GCD 2-Core Processor Block A Block B Thread 1
Block C Thread 3 Block D Thread Pool Concurrent Queue Thread 2 -
Block Delegation Delegates are protocols that specify method callbacks These
method callbacks can be easily replaced with block callbacks Rule of thumb: 3 or less delegate callbacks should be handled using blocks. -
Block Delegation Advantages: No additional protocols No need to conform
to a protocol Easy to change block callbacks dynamically -
Block Delegation Disadvantages: May not be appropriate when more than
3 callbacks are needed -
Template Method Sometimes algorithms differ by only a few lines
Those few lines can be replaced by a virtual method Subclasses define the virtual method -
Template Method Let’s use blocks instead! Advantages: No need for
subclassing May not need a new class at all Template can be changed dynamically -
Lock Handling // Python convenient lock handler with myLock: //
Do something // Can’t forget to lock/unlock -
Lock Handling + (void)withLock:(NSLock)lock executeBlock:(MyBlock)block { NSParameterAssert(lock && block); [lock
lock]; block(); [lock unlock]; } -
Builder Pattern Some objects have complex structures that vary widely
between uses Examples: Database tables, UITableView A block can define the construction of an object -
Builder Pattern // Ruby on Rails database table construction create_table
:person do |t| t.string :name, null: false t.integer :age, null: false t.float :weight end -
Builder Pattern // Contrived example, but very feasible. // Creating
a custom table view form. [RBTableView tableViewUsingBlock:^(RBTableView * t) { [t addStringField:@“name” required:YES]; [t addIntegerField:@“age” required:YES]; [t addFloatField:@“weight” required:NO]; }]; -
Lockless Exclusion Accessor Thread-sensitive code is called critical code GCD
allows for lockless-exclusion Lockless code is faster than lock code -
Lockless Exclusion Accessor Multi-threading challenges: Multi-threaded code must be properly
synchronized It’s easy to forget to add thread-safety code -
Lockless Exclusion Accessor Solution: Have another object handle thread-safety Put
critical code in a block Give the block to the thread-safety handler -
Lockless Exclusion Accessor - (void)accessSharedDataAsync:(void(^)(id sharedData))block { NSParameterAssert(block); // myQueue
must be a serial queue! dispatch_async(myQueue, ^{ block([self sharedData]); }); } -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Block A Block B Shared Data Serial Queue Block C
Shared Data Manager Lockless Exclusion Accessor LEA -
Read/Write LEA Code that only reads from shared memory doesn’t
need exclusive access Code that writes to shared memory needs exclusive access GCD barrier blocks and concurrent queues are a perfect solution -
Read/Write LEA // Read-only LEA - (void)accessSharedDataReadOnlyAsync:(void(^)(id sharedData))block { NSParameterAssert(block);
// myQueue must be a concurrent queue! dispatch_async([self myQueue], ^{ block([self sharedData]); }); } -
Read/Write LEA // Read/write LEA - (void)accessSharedDataReadWriteAsync:(void(^)(id sharedData))block { NSParameterAssert(block);
// myQueue must be a concurrent queue! dispatch_barrier_async([self myQueue], ^{ block([self sharedData]); }); } -
// All-in-one R/W-LEA - (void)accessSharedDataAsyncReadOnly:(BOOL)readOnly withBlock:(void(^)(id sharedData))block { if (readOnly)
dispatch_async([self myQueue], ^{ block([self sharedData]); }); else dispatch_barrier_async([self myQueue], ^{ block([self sharedData]); }); } Read/Write LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Read/Write LEA Shared Data Concurrent Queue Read Block Read Block
Write Block Read Block Read Block Shared Data Manager R/W-LEA -
Homework I have only touched the surface of what blocks
can do Explore and find other design patterns you can convert to use blocks -
Want to Learn More? Blocks are best learned by playing
with them Learn Ruby “The Pickaxe” Learn Racket 10 Uses for Blocks My Blog