Fundamentals of Blocks
Introduced in iOS 4, Blocks are a powerful C-language extension anabling anonymous function functionality. They behave as unique data types that can be assigned to variables, passed as arguments, or returned from functions. Beyond simple storage, blocks encapsulate executable code segments that can be invoked later when needed. In modern iOS engineering, they are foundational for GCD (Grand Central Dispatch), animations, sorting logic, and callback mechenisms.
Important: Assigning a block stores the logic; execution only occurs upon explicit invocation.
Declaration and Invocation
Variable Definition
The syntax follows ReturnType (^BlockName)(Parameters):
// Define a void block taking two NSString parameters
void (^myLogger)(NSString *, NSString *);
Parameter names within declarations can be omitted if only types matter:
void (^calculation)(int firstValue, int secondValue);
Assignment and Execution
Assignments use the caret ^ symbol followed by parameters and a brace-enclosed body:
myLogger = ^(NSString *first, NSString *second) {
NSLog(@"%@ said %@", first, second);
};
// Invoke the stored block
myLogger(@"Alice", @"Hello");
Return types can often be omitted during assignment if inferred:
// Integers are explicitly typed here
int (^multiplier)(int) = ^(int val) {
return val * 7;
};
NSLog(@"Result: %d", multiplier(9));
Simplifying with Typedef
Repeatedly typing complex signature definitions is verbose. Use typedef to create aliases:
typedef void (^CompletionHandler)(BOOL success);
CompletionHandler taskFinished = ^{
NSLog(@"Process complete");
};
Passing Blocks as Arguments
Blocks act similarly to objects when passed through functions.
C Function Interface
void runOperation(int(^operation)(int, int)) {
NSLog(@"Output: %d", operation(10, 20));
}
int(^addFunc)(int, int) = ^(int a, int b) { return a + b; };
runOperation(addFunc);
// Inline definition usage
runOperation(^(int x, int y) { return x * y; });
Objective-C Method Interface
- (void)processWith:(int(^)(int, int))callback {
NSLog(@"Total: %d", callback(5, 5));
}
[self processWith:^int(int x, int y) {
return x + y;
}];
Using a typedef improves readability in headers:
typedef int (^MathOp)(int, int);
- (void)calculateWith:(MathOp)op;
Variable Capture Rules
Understanding how blocks interact with surrounding scope is critical.
Capturing Local Variables
Local variables captured by a block are constant copies (immutable):
int limit = 50;
void (^checkLimit)() = ^{
NSLog(@"Limit is: %d", limit); // Valid read
};
limit = 60;
checkLimit(); // Outputs 50, not 60
Direct modification inside the block is prohibited without modifiers:
void (^modifyCheck)() = ^{
limit++; // Compilation Error
};
Using __block
To modify captured local variables or reflect external changes, apply the __block keyword:
__block int dynamicValue = 50;
void (^updateBlock)() = ^{
dynamicValue = 60; // Allowed
NSLog(@"Value: %d", dynamicValue);
};
dynamicValue = 70;
updateBlock(); // Outputs 70
Global and Static Scope
Unlike locals, global and static variables do not get copied. Accessing them reflects current state, and modifications persist across calls.
static int staticCounter = 100;
void (^countBlock)() = ^{
staticCounter++;
NSLog(@"Static: %d", staticCounter);
};
Memory Management: MRC vs ARC
Manual Reference Counting (MRC)
Stack Allocation: Blocks created inline reside on the stack until copied. They must remain within the method scope where defined.
Heap Promotion (copy):
If you copy a block, it moves to the heap. The system will not automatically release it; manual Block_release is required in MRC.
Retain Behavior: When copying a block that captures an object, that object is retained. This can lead to leaks if the block persists longer than the object's expected lifespan, or cycles.
Automatic Reference Counting (ARC)
Under ARC, blocks move to the heap automatically up on leaving the stack scope. Retainment rules remain similar regarding objects captured inside.
The Retain Cycle Problem: A common issue arises when an object holds a reference to a block, and that block captures the owning object strongly.
@interface Handler : NSObject
@property (nonatomic, copy) void (^task)(void);
@end
@implementation Handler
// ... implementation
@end
Handler *handler = [[Handler alloc] init];
handler.task = ^{
NSLog(@"Working...");
};
If self is used inside the block property assignment, a cycle forms because self retains handler, and handler retains the block which retains self.
Resolving Circular References
ARC Strategy: Weak References
Instead of __block (which doesn't prevent strong retentions under ARC), use a weak pointer outside the block definition.
Handler *strongHandler = [Handler new];
__weak Handler *weakSelf = strongHandler;
strongHandler.task = ^{
__strong Handler *strongSelf = weakSelf;
if (strongSelf) {
// Safe interaction
[strongSelf performWork];
}
};
Modal Presentation Pattern
When presenting view controllers via blocks:
MyViewController *controller = [[MyViewController alloc] init];
__weak MyViewController *weakController = controller;
controller.handler = ^(NSInteger result) {
if (weakController) {
[weakController dismissViewControllerAnimated:YES completion:nil];
}
};
[self presentViewController:controller animated:YES completion:^{}];
This ensures the presented controller does not inadvertently retain itself after dismissal.