Block objects are a C-based language feature that you can use in your C, Objective-C, and C++ code. Blocks make it easy to define a self-contained unit of work. Although they might seem akin to function pointers, a block is actually represented by an underlying data structure that resembles an object and is created and managed for you by the compiler. The compiler packages up the code you provide (along with any related data) and encapsulates it in a form that can live in the heap and be passed around your application.
One of the key advantages of blocks is their ability to use variables from outside their own lexical scope. When you define a block inside a function or method, the block acts as a traditional code block would in some ways. For example, a block can read the values of variables defined in the parent scope. Variables accessed by the block are copied to the block data structure on the heap so that the block can access them later. When blocks are added to a dispatch queue, these values must typically be left in a read-only format. However, blocks that are executed synchronously can also use variables that have the __block
keyword prepended to return data back to the parent’s calling scope.
You declare blocks inline with your code using a syntax that is similar to the syntax used for function pointers. The main difference between a block and a function pointer is that the block name is preceded with a caret (^
) instead of an asterisk (*
). Like a function pointer, you can pass arguments to a block and receive a return value from it.
Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. Blocks are Objective-C objects,which means they can be added to collections like NSArray
or NSDictionary
. They also have the ability to capture values from the enclosing scope, making them similar to closures or lambdas in other programming languages.
声明block
void (^simpleBlock)(void);
simpleBlock = ^{ |
NSLog(@"This is a block"); |
}; |
You can also combine the variable declaration and assignment:
void (^simpleBlock)(void) = ^{ |
NSLog(@"This is a block"); |
}; |
Once you’ve declared and assigned a block variable, you can use it to invoke the block:
simpleBlock(); |
Blocks Take Arguments and Return Values
Blocks can also take arguments and return values just like methods and functions.
As an example, consider a variable to refer to a block that returns the result of multiplying two values:
double (^multiplyTwoValues)(double, double); |
The corresponding block literal might look like this:
^ (double firstValue, double secondValue) { |
return firstValue * secondValue; |
} |
Once you’ve declared and defined the block, you can invoke it just like you would a function:
double (^multiplyTwoValues)(double, double) = |
^(double firstValue, double secondValue) { |
return firstValue * secondValue; |
}; |
|
double result = multiplyTwoValues(2,4); |
Blocks Can Capture Values from the Enclosing Scope
Only the value is captured, unless you specify otherwise. This means that if you change the external value of the variable between the time you define the block and the time it’s invoked, like this:
int anInteger = 42; |
|
void (^testBlock)(void) = ^{ |
NSLog(@"Integer is: %i", anInteger); |
}; |
|
anInteger = 84; |
|
testBlock(); |
the value captured by the block is unaffected. This means that the log output would still show:
Integer is: 42 |
It also means that the block cannot change the value of the original variable, or even the captured value (it’s captured as a const
variable).
Use __block Variables to Share Storage
If you need to be able to change the value of a captured variable from within a block, you can use the __block
storage type modifier on the original variable declaration. This means that the variable lives in storage that is shared between the lexical scope of the original variable and any blocks declared within that scope.
As an example, you might rewrite the previous example like this:
__block int anInteger = 42; |
|
void (^testBlock)(void) = ^{ |
NSLog(@"Integer is: %i", anInteger); |
}; |
|
anInteger = 84; |
|
testBlock(); |
Because anInteger
is declared as a __block
variable, its storage is shared with the block declaration. This means that the log output would now show:
Integer is: 84 |
It also means that the block can modify the original value, like this:
__block int anInteger = 42; |
|
void (^testBlock)(void) = ^{ |
NSLog(@"Integer is: %i", anInteger); |
anInteger = 100; |
}; |
|
testBlock(); |
NSLog(@"Value of original variable is now: %i", anInteger); |
This time, the output would show:
Integer is: 42 |
Value of original variable is now: 100 |
You Can Pass Blocks as Arguments to Methods or Functions
Blocks make this much easier, however, because you can define the callback behavior at the time you initiate the task, like this:
- (IBAction)fetchRemoteInformation:(id)sender { |
[self showProgressIndicator]; |
|
XYZWebTask *task = ... |
|
[task beginTaskWithCallbackBlock:^{ |
[self hideProgressIndicator]; |
}]; |
} |
The (void (^)(void))
specifies that the parameter is a block that doesn’t take any arguments or return any values. The implementation of the method can invoke the block in the usual way:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock { |
... |
callbackBlock(); |
} |
Method parameters that expect a block with one or more arguments are specified in the same way as with a block variable:
- (void)doSomethingWithBlock:(void (^)(double, double))block { |
... |
block(21.0, 2.0); |
} |
A Block Should Always Be the Last Argument to a Method
It’s best practice to use only one block argument to a method. If the method also needs other non-block arguments, the block should come last:
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback; |
This makes the method call easier to read when specifying the block inline, like this:
[self beginTaskWithName:@"MyTask" completion:^{ |
NSLog(@"The task is complete"); |
}]; |
Use Type Definitions to Simplify Block Syntax
As an example, you can define a type for a simple block with no arguments or return value, like this:
typedef void (^XYZSimpleBlock)(void); |
You can then use your custom type for method parameters or when creating block variables:
XYZSimpleBlock anotherBlock = ^{ |
... |
}; |
Objects Use Properties to Keep Track of Blocks
The syntax to define a property to keep track of a block is similar to a block variable:
@interface XYZObject : NSObject |
@property (copy) void (^blockProperty)(void); |
@end |
Note: You should specify copy
as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.
A block property is set or invoked like any other block variable:
self.blockProperty = ^{ |
... |
}; |
self.blockProperty(); |
It’s also possible to use type definitions for block property declarations, like this:
typedef void (^XYZSimpleBlock)(void); |
|
@interface XYZObject : NSObject |
@property (copy) XYZSimpleBlock blockProperty; |
@end |
If you need to capture self
in a block, such as when defining a callback block, it’s important to consider the memory management implications.
Blocks maintain strong references to any captured objects, including self
, which means that it’s easy to end up with a strong reference cycle if, for example, an object maintains a copy
property for a block that captures self
:
@interface XYZBlockKeeper : NSObject |
@property (copy) void (^block)(void); |
@end |
@implementation XYZBlockKeeper |
- (void)configureBlock { |
self.block = ^{ |
[self doSomething]; // capturing a strong reference to self |
// creates a strong reference cycle |
}; |
} |
... |
@end |
The compiler will warn you for a simple example like this, but a more complex example might involve multiple strong references between objects to create the cycle, making it more difficult to diagnose.
To avoid this problem, it’s best practice to capture a weak reference to self
, like this:
- (void)configureBlock { |
__weak XYZBlockKeeper *weakSelf = self; |
self.block = ^{ |
[weakSelf doSomething]; // capture the weak reference |
// to avoid the reference cycle |
} |
} |
By capturing the weak pointer to self
, the block won’t maintain a strong relationship back to the XYZBlockKeeper
object. If that object is deallocated before the block is called, the weakSelf
pointer will simply be set to nil
.
Blocks Can Simplify Enumeration
In addition to general completion handlers, many Cocoa and Cocoa Touch API use blocks to simplify common tasks, such as collection enumeration. The NSArray
class, for example, offers three block-based methods, including:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block; |
This method takes a single argument, which is a block to be invoked once for each item in the array:
NSArray *array = ... |
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) { |
NSLog(@"Object at index %lu is %@", idx, obj); |
}]; |
The block itself takes three arguments, the first two of which refer to the current object and its index in the array. The third argument is a pointer to a Boolean variable that you can use to stop the enumeration, like this:
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) { |
if (...) { |
*stop = YES; |
} |
}]; |
It’s also possible to customize the enumeration by using the enumerateObjectsWithOptions:usingBlock:
method. Specifying the NSEnumerationReverse
option, for example, will iterate through the collection in reverse order.
If the code in the enumeration block is processor-intensive—and safe for concurrent execution—you can use theNSEnumerationConcurrent
option:
[array enumerateObjectsWithOptions:NSEnumerationConcurrent |
usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) { |
... |
}]; |
This flag indicates that the enumeration block invocations may be distributed across multiple threads, offering a potential performance increase if the block code is particularly processor intensive. Note that the enumeration order is undefined when using this option.
The NSDictionary
class also offers block-based methods, including:
NSDictionary *dictionary = ... |
[dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) { |
NSLog(@"key: %@, value: %@", key, object); |
}]; |
This makes it more convenient to enumerate each key-value pair than when using a traditional loop, for example.