mick@next:~/objc/blocks-closures-objc$/* Rediscovering the language that built the future */
Blocks - Closures Before Swift Existed
objc / blocks-closures-objc.m

Blocks - Closures Before Swift Existed

Mick MacCallumMick MacCallum
@Objective-C@Concurrency@Fundamentals

In 2010, Apple added blocks to Objective-C. They're closures—anonymous functions that capture their environment. Swift closures descend directly from this feature, and understanding blocks helps you work with older codebases and APIs.

Block Syntax

The infamous block syntax trips up many developers. Let's break it down:

// A block that takes no arguments and returns nothing
void (^simpleBlock)(void) = ^{
    NSLog(@"Hello from a block");
};

// A block that takes two ints and returns their sum
int (^addBlock)(int, int) = ^(int a, int b) {
    return a + b;
};

The caret (^) marks block literals. The type declaration follows a pattern:

return_type (^block_name)(parameter_types)

Using Blocks

Call blocks like functions:

simpleBlock();  // Prints "Hello from a block"
int result = addBlock(3, 4);  // result = 7

Blocks are objects under the hood. You can store them in properties, arrays, and dictionaries:

@property (nonatomic, copy) void (^completionHandler)(BOOL success);

self.completionHandler = ^(BOOL success) {
    NSLog(@"Operation %@", success ? @"succeeded" : @"failed");
};

Always use copy for block properties. This ensures they move from the stack to the heap.

Capturing Variables

Blocks capture variables from their enclosing scope:

NSString *greeting = @"Hello";

void (^greetBlock)(NSString *) = ^(NSString *name) {
    NSLog(@"%@, %@!", greeting, name);  // Captures greeting
};

greeting = @"Goodbye";  // This change doesn't affect the block
greetBlock(@"World");   // Prints "Hello, World!"

By default, captured variables are const copies. The block sees the value at capture time, not later changes.

The __block Modifier

To modify captured variables, use __block:

__block int counter = 0;

void (^incrementBlock)(void) = ^{
    counter++;  // Can modify because of __block
};

incrementBlock();
incrementBlock();
NSLog(@"%d", counter);  // Prints 2

The __block storage qualifier creates a shared mutable variable between the block and enclosing scope.

Blocks as Parameters

Blocks shine as callback parameters:

- (void)fetchDataWithCompletion:(void (^)(NSData *data, NSError *error))completion {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSData *data = [self loadData];
        NSError *error = nil;

        dispatch_async(dispatch_get_main_queue(), ^{
            completion(data, error);
        });
    });
}

// Usage
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
    if (error) {
        [self showError:error];
    } else {
        [self displayData:data];
    }
}];

Typedef for Clarity

Complex block types benefit from typedef:

typedef void (^DataCompletionBlock)(NSData *data, NSError *error);
typedef BOOL (^FilterBlock)(id object);
typedef id (^TransformBlock)(id input);

// Now cleaner declarations
- (void)fetchDataWithCompletion:(DataCompletionBlock)completion;
- (NSArray *)filter:(FilterBlock)predicate;

Blocks vs Function Pointers

Blocks are more powerful than C function pointers because they capture state:

// Function pointer - no captured state
int (*funcPtr)(int) = &someFunction;

// Block - captures multiplier from its environment
int multiplier = 7;
int (^multiplyBlock)(int) = ^(int num) {
    return num * multiplier;
};

The block carries multiplier with it wherever it goes.

Memory and Retain Cycles

Blocks retain captured objects. If the block is retained by one of those objects, you get a cycle:

// Creates a retain cycle
self.completionHandler = ^{
    [self doSomething];  // Block retains self, self retains block
};

// Break with weak reference
__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
    [weakSelf doSomething];  // Block captures weak reference
};

For blocks that might execute after self could deallocate, use the weak-strong dance:

__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) return;

    [strongSelf doSomething];
    [strongSelf doSomethingElse];
};

Block Storage Classes

Blocks exist in three forms:

Global blocks have no captured state and live in static memory:

void (^globalBlock)(void) = ^{
    NSLog(@"No captured variables");
};

Stack blocks capture variables and initially live on the stack:

int x = 10;
void (^stackBlock)(void) = ^{
    NSLog(@"%d", x);
};

Heap blocks are stack blocks that have been copied:

void (^heapBlock)(void) = [stackBlock copy];

ARC automatically copies blocks when needed (assigning to properties, returning from methods). Under MRC, you must copy manually.

Common Patterns

Enumeration:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([obj isEqual:target]) {
        *stop = YES;  // Stop enumeration
    }
}];

Sorting:

NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    return [a compare:b];
}];

Animation:

[UIView animateWithDuration:0.3 animations:^{
    self.view.alpha = 0;
} completion:^(BOOL finished) {
    [self.view removeFromSuperview];
}];

From Blocks to Swift Closures

When you see this block:

^(NSString *input) {
    return input.length;
}

The Swift equivalent is:

{ (input: String) -> Int in
    return input.count
}

Same concept, cleaner syntax. If you understand blocks, Swift closures are straightforward.

Blocks transformed Objective-C when they arrived. Asynchronous APIs became cleaner, collection operations became expressive, and callback patterns improved dramatically. They're a bridge between Objective-C's heritage and Swift's future.