Вы находитесь на странице: 1из 32

Objective C Blocks

Asfar
Block
A chunk of code that can be treated like objective
C object.
Portable and anonymous object encapsulating a
unit of work that can be performed
asynchronously at a later time.
A block can be assigned to a variable, passed as
an argument to a function or method, added to
collections like NSDictionary or NSArray and
(unlike most other types) executed inline.
They are similar to closures and lambdas
Alternative to the delegate pattern or to callback
functions.
Why use blocks
Aides Functional programming.
Make code concise and easier to read.
Can define simple action related to object at the same
scope the object is created, instead of defining a
separate method.
Flexible to change since they dont require separate
methods and protocols.
System framework methods uses blocks as parameters.
Completion handlers
Notification handlers
Error handlers
Enumeration
View animation and transitions
Sorting
Blocks in code
Like a function, a block can take one or more parameters
and specify a return value.
To declare a block variable, use the caret (^) symbol.
void (^loggerBlock)(void);
To define the block, you do the same, but follow it up
with the actual code defining the block wrapped in curly
braces.
loggerBlock = ^{ NSLog(@Inside a block"); };
Execute the block, just like calling a function.
loggerBlock();
block literal
^(int a, int b)
{
int powers = a ** b;
return powers;
}
Similar to a C function body, except for these differences:
There is a caret symbol preceding the function body.
Return types are automatically inferred when not defined.
There is no function name. We say that block literals are
anonymous.
functions are defined in the global scope, but blocks are defined
in a local scope.
blocks can be defined anywhere a variable can.
When defining a block, youre creating a block literal
A literal is a value that can be built at compile time similar to integer literal
3.
Block pointers/reference
Global Blocks
At a file level, you can use a block as a global
literal:

#import <stdio.h>

int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
inline blocks
(void) simpleMethod{

__block NSInteger outsideVariable = 10;


NSInteger integerValue = 10;
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1", @"obj2", nil];

[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {


NSInteger insideVariable = 20;
outsideVariable = 30;
NSLog(@"Outside variable = %l", outsideVariable);
NSLog(@"Inside variable = %l,insideVariable);
/* Return value for our block object */ return
NSOrderedSame;
}];
}
Types of Variables and use in block
Global variables, including static variables that exist
within the enclosing scope are accessible
similar to function.
local (non-static) variables local to the enclosing lexical
scope are captured as const variables
Their values are taken at the point of the block expression
within the program.
In nested blocks, the value is captured from the nearest
enclosing scope.
Local variables declared within the lexical scope of the
block, which behave exactly like local variables in a
function
Each invocation of the block provides a new copy of that
variable. These variables can in turn be used as const or
by-reference variables in blocks enclosed within the block.
__block Storage type
These are mutable within the block (and the enclosing scope)
and are preserved if any referencing block is copied to the
heap.
Without __block
int spec = 4;
typedef int (^MyBlock)(int);

MyBlock InBlock = ^(int aNum){


return aNum * spec;
};
spec = 0;
printf("InBlock value is %d",InBlock(4)); //prints InBlock value is 16

With __block
__block int spec = 4;
typedef int (^MyBlock)(int);

MyBlock InBlock = ^(int aNum){


return aNum * spec;
};
spec = 0;
printf("InBlock value is %d",InBlock(4)); //prints InBlock value is 0
block implementation
A block is implemented internally using two pieces:
compiled code in the .text segment of the executable
(like a normal function)
a data structure that predominantly contains the
values of the variables that the block uses from its
surrounding scope and isa pointer (like normal
objects)
But they implement only the -copy, -release and -
autorelease methods.
They also implement retain for adding to collections,
but you should always use -copy instead.
Because a stack-based object will not be copied to
the heap if you send it a -retain message; it will only
be copied upon receipt of the -copy message.
Blocks stored in stack/heap
All blocks are initially created on the stack.
If they arent copied then they will remain on stack
for their entire lifetime.
While stack-based, a block has no effect on the
storage or lifetime of anything it accesses.
Patterns to Avoid
A block literal is the address of a stack-local
data structure that represents the block.
The scope of the stack-local data structure is
therefore the enclosing compound statement.
When adding block pointers to a collection,
you need to copy them first using copy
function.
Avoid these:
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 block references

for (int i = 0; i < 3; ++i) {


blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop.
}
}

void dontDoThis() {
void (^block)(void);

int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause.
}
// ...
}
Blocks can be copied to the heap
If you want to keep a block around, it can be
copied to the heap
This is an explicit operation.
When copied to the heap, a block will gain
proper reference-counting in the manner of
Objective-C objects.
When they are first copied, they take their
captured scope with them, retaining any
objects as necessary to keep them around.
Copying Blocks

When you copy a block, any references to


other blocks from within that block are copied
if necessaryan entire tree may be copied (from
the top).
If you have block variables and you reference
a block from within the block, that block will
be copied.
__block variables live in storage that is shared between the
scope of the variable and all blocks
So, the storage will survive the destruction of the stack frame
if any copies of the blocks declared within the frame survive
beyond the end of the frame
(for example, by being enqueued somewhere for later
execution).
Multiple blocks in a given scope can simultaneously use a shared
variable.
As an optimization, block storage starts out on the stack
If the block is copied, variables are copied to the heap.
So, the address of a __block variable can change over time.
Block is copied to heap

Two copies of the block now exist, each with their own const
copy of the local variable.
The shared variable still exists in one place, but that place has
now moved from the stack to the heap.
Blocks and Memory Management
Variables moved to the heap have a reference count, and
are retained by each copy of a block which references
them.
In this case the shared variable has a retain count of 2:
one for the stack-based block
one for the heap-based block.

If the stack unrolls, the block there will release its reference
on the shared variable automatically leaving it with a
reference count of 1.
If the heap-based block is deallocated first, it will do the same,
leaving the shared variable with a reference count of 1.
When both blocks have been deallocated, the shared
variable will also be released.
Objects and __block
The only time youll need to use the __block
storage specifier for an ObjC object is when
you want to assign it a value directly in blocks.
Objects are pointers to memory allocated on
the heap, so your blocks will be able to use
them automatically.
When a block is copied, the runtime will retain
any Objective-C types automatically, and it will
release them when the block is de-allocated.
function pointer vs block
blocks can
Be defined in-line in your code.
define at the point where its going to be passed to
another method or function.
access(read) variables available in the scope where its
created.
By default, the block makes a copy of any variable you
access this way, leaving the original intact
but you can make an outside variable read/write by
prepending the storage qualifier __block before its
declaration.
Even after the method or function enclosing a block
has returned and its local scope is destroyed, the local
variables persist as part of the block object as long as
there is a reference to the block.
Object and Block Variables
When a block is copied, it creates strong references to object
variables used within the block.
If you use a block within the implementation of a method:
If you access an instance variable by reference, a strong
reference is made to self;

dispatch_async(queue, ^{
// instanceVariable is used by reference, a strong reference is made to self
doSomethingWithObject(instanceVariable);
});
If you access an instance variable by value, a strong
reference is made to the variable.

id localVariable = instanceVariable;
dispatch_async(queue, ^{
/*
localVariable is used by value, a strong reference is made to localVariable
(and not to self).
*/
doSomethingWithObject(localVariable);
});

To override this behavior for a particular object variable, you can mark it
with the __block storage type modifier.
Retain cycle in blocks
All the variables from the surrounding scope used
inside the block will be retained
[self someMethodWithBlock:^{
[self someOtherMethod]; }];

Here the block is held by the instance and the block


itself is strongly holding self. This is a case of a retain
cycle that will lead to a memory leak.
To avoid it define a weak self before calling the block
__weak id wself = self;
[self someMethodWithBlock:^{
[wself someOtherMethod]; }];
self
Accessing self in inline block objects is fine as
long as self is defined in the scope inside
which the inline block object is created.
You cannot access self in an independent
block object.
void (^incorrectBlockObject)(void) = ^{
NSLog(@"self = %@", self); /* self is undefined here */
};
declared object properties
For inline block objects, you can use dot notation
to read from or write to declared properties of
self
- (void) simpleMethod{
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1", @"obj2", nil];
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSLog(@"self = %@", self);
self.stringProperty = @"Block Objects";
NSLog(@"String property = %@", self.stringProperty);
/* Return value for our block object */
return NSOrderedSame;
}];
}
In an independent block object, you cannot
use dot notation to read from or write to a
declared property:
void (^incorrectBlockObject)(id) = ^(id self){

NSLog(@"self = %@", self);

/* Should use setter method instead of this */


self.stringProperty = @"Block Objects"; /* Compile-time Error */

/* Should use getter method instead of this */


NSLog(@"self.stringProperty = %@", self.stringProperty);
/* Compile-time Error */
};
Instead of dot notation in this scenario, use
the getter and the setter methods of this
property:

void (^correctBlockObject)(id) = ^(id self){


NSLog(@"self = %@", self);
/* This will work fine */
[self setStringProperty:@"Block Objects"];
/* This will work fine as well */
NSLog(@"self.stringProperty = %@",
[self stringProperty]);
};
Dispatch queues heart of GCD.
Add tasks to these queues and asking the queues to execute your
tasks.
ways to submit tasks to dispatch queues:
Block objects
C functions
We will simply place our code in a block object/c function and
dispatch that to GCD for execution.
Execute tasks in a first-in-first-out (FIFO) fashion
several options for running tasks: synchronously, asynchronously,
after a certain delay, etc.
We can specify whether that code gets executed on the main
thread or any other thread.
pools of threads (with runloop) managed by GCD on the host
operating system. You will not be working with these threads
directly.
Dispatch queues
Main queue
Concurrent queues
Serial queues
only execute one block object at a time
they do not run on the main thread
Used for a series of tasks that have to be executed
in strict order without blocking the main thread
Can be used for synchronization
Blocks and dispatch queues
For blocks that run asynchronously using a dispatch queue,
Safe to capture scalar variables (int, float etc) from the parent
function or method and use them in the block.
Not safe to capture large structures or other pointer-based
variables that are allocated and deleted by the calling context.
By the time your block is executed, the memory referenced by
that pointer may be gone.
Safe to allocate memory (or an object) yourself and explicitly
hand off ownership of that memory to the block.
Dispatch queues copy blocks that are added to them, and
they release blocks when they finish executing.
no need to explicitly copy blocks before adding them to a
queue.
Enclose blocks code in an @autorelease block to handle
the memory management for those objects.