From Blocks to Closures: How Apple Brought Closures to C, Objective-C and Swift
Before Swift existed, Apple developers already had closures. They were called blocks, and they arrived as an Apple extension to C, Objective-C and C++. If you maintain older code, or you wonder why so many Cocoa APIs take a completion handler, blocks are where that story starts.
What a block is
Apple’s Blocks Programming Topics guide describes a block as a chunk of code bundled with the data it needs, which makes blocks handy as callbacks. According to that guide, blocks shipped in the GCC and Clang compilers that came with the OS X v10.6 Xcode tools, and you can use them on OS X v10.6 and later and on iOS 4.0 and later. The blocks runtime is open source and lives in LLVM’s compiler-rt project, and Apple also put the idea before the C standards working group as N1370, “Apple’s Extensions to C”.
The syntax is built around the caret. A block literal looks like a function body with ^ in front of it, and a block variable is declared much like a function pointer:
// A block that takes two ints and returns their sum
int (^add)(int, int) = ^int(int a, int b) {
return a + b;
};
int total = add(2, 3); // 5
Capturing values, and the __block qualifier
A block captures the variables it uses from the surrounding scope. By default it captures a copy of each local variable’s value at the moment the block is created, so changing the variable afterwards does not affect the block, and the block cannot assign to it. When you do want shared, mutable storage, the Clang block language specification provides the __block storage qualifier:
__block NSInteger taps = 0;
void (^recordTap)(void) = ^{
taps += 1; // allowed because of __block
};
recordTap();
recordTap();
// taps == 2
Why blocks spread so quickly: GCD
Grand Central Dispatch (libdispatch) first shipped with Mac OS X 10.6 and is available on iOS 4 and later. Its API is built around submitting blocks of work to queues, which gave developers a compact way to move work off the main thread and come back when it finished:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[self showData:data];
});
});
Once GCD and new Foundation and UIKit APIs accepted blocks, the delegate-and-selector pattern started giving way to completion handlers in many places.
The classic pitfall: retain cycles
Blocks that are stored for later keep strong references to the objects they capture. If an object owns a block and that block captures self, neither can be released. The usual Objective-C fix is to capture a weak reference:
__weak typeof(self) weakSelf = self;
self.onFinish = ^{
[weakSelf reloadView];
};
The same ideas in Swift
Swift closures are the direct descendants of blocks, and Swift code can pass closures to Objective-C APIs that expect blocks. The Swift book’s chapter on closures covers the same ground with lighter syntax: closure expressions, trailing closures, capturing values, and the @escaping attribute for closures that outlive the function call.
let add: (Int, Int) -> Int = { a, b in a + b }
DispatchQueue.global(qos: .utility).async {
let data = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
self?.show(data)
}
}
A few equivalences make old code easier to read:
^{ … }in Objective-C corresponds to{ … }in Swift.- A stored completion block corresponds to an
@escapingclosure parameter. __weak typeof(self) weakSelfcorresponds to a[weak self]capture list.__blockhas no direct Swift equivalent because Swift closures capture variables by reference already.
Takeaway
Blocks turned C-family code on Apple platforms into something that could pass behaviour around as easily as data. Swift kept the model and cleaned up the syntax, so understanding blocks still pays off whenever you open an older Objective-C project.