SW engineering, engineering management and the business of software

subscribe for more
stuff like this:

Objective C Blocks: Summary, Syntax & Best Practices

Blocks are closures for C, Objective C and C++. You may know them as anonymous functions or lambda expressions.

Good usage of blocks is an excellent path to reducing typing, line count and bug count in your Cocoa programs.

Blocks should not be confused with Grand Central Dispatch (GCD): GCD is primarily a queue library, which typically uses blocks.

Blocks do have a learning curve attached to them. They also have a tremendously wonky syntax. Buyer beware: in simple situations, blocks are very readable, but excessively nested blocks can transform your source into inscrutable rivers of punctuation and indentation.

Blocks: The Good

Blocks are closures for C.

We’ve briefly threw out the above quote earlier, but it’s time to explore why closures are a good thing.

This particular example is a simple implementation of a callback. Prior to Snow Leopard, Objective C callbacks were implemented in one of two ways. The first is simply passing a selector and a target, as in the example below. The alternative method of doing callbacks using delegates is even more verbose.

The classic example is that prior to blocks, Apple documentation had roughly 1,300 words dedicated to getting setting up a delegate and getting data from a URL:Using NSURLConnection.

Now compare this with the more recent blocks based solution:

id someOtherClass;
[NSURLConnection sendAsynchronousRequest:request
                                   queue:[[NSOperationQueue alloc] init]
                       completionHandler:^(NSURLResponse *resp, NSData *data, NSError *err) {
                           // Do something with the data
                           // Because this block "closes around" and captures the surround scope,
                           // you can use someOtherClass in this block if necessary.
                       }];

The magic of blocks is that all the variables in the same scope as the block are kept with the block as it gets passed to the networkManager and back. This is called a closure. Block-based APIs are more typically more resilient. If a block is passed around a few times, and at some point you realize you need an instance variable from two or three objects ago, it’s likely still in the block. A delegate based API would likely have to rewrite the delete protocol as well as any objects that conform to that protocol.

Most of the other block-based goodness comes from combining it’s super-powers with Grand Central Dispatch queues.

Blocks: The Bad

Blocks have a somewhat steep learning curve.

Oddly enough, newcomers to the platform have an advantage here. Long time Cocoa-heads likely have to break ingrained habits to clearly see where blocks can help them.

Here are some hints:

  • Callbacks of any kind are likely candidates for using blocks.
  • Anywhere you have a formal or informal delegate, blocks are a good candidate for replacement.
  • Iterating through a collection can be done via a block instead of a for loop. This is especially applicable when the order doesn’t matter and the enumeration can be done concurrently.
  • Completion handlers or failure handlers. (assetForURL:resultBlock:failureBlock: is a nice example of this.)

Blocks: The Ugly

Blocks are ugly.

Actual Block syntax:

NSError * (^workerBlock)(NSString *someString, BOOL(^afterWorkBlock)(int));

The above defines a block that takes two arguments, an NSString and a afterWorkBlock and returns an NSError pointer. The afterWorkBlock takes an int and returns a BOOL.

Ugh.

The languages designers are constrained by the existing C, C++ and Objective C languages. According to an Apple developer, the caret (^) was chosen because it is the only character you can’t overload in C++.

Let’s breakdown the two primary types of block syntax:

  • Block literals
  • Block pointers

Block literals are defined inline with your code. Here is an example of directly calling a method, passing a block literal as an argument:

NSUInteger foundAtIndex = [someArray indexOfObjectPassingTest:^ BOOL (id object, NSUInteger idx, BOOL *stop) {
    return [object hasPrefix:@"SOME_PREFIX"];
}];

Block pointers look similar to function pointers, but use the ^ (caret) instead of the * (asterisk/star/splat). Here is an example of assigning a block literal to a block pointer:

// checkMatch is a block pointer that we assign to.
BOOL (^checkMatch)(id, NSUInteger, BOOL *) = ^ BOOL (id object, NSUInteger idx, BOOL *stop) {
    return [object hasPrefix:@"SOME_PREFIX"];
};

NSUInteger foundPrefixAtIndex = [someArray indexOfObjectPassingTest:checkMatch];

It’s important to note that block literals and block pointers are ordered slightly differently:

  • Block Pointer:

    returnType (^blockPointerName) (argTypes...) // argNames are optional
    returnType (^blockPointerName) (argTypes argNames...)
    void (^myBlockPtr) (id)
    
  • Block Literal:

    ^ returnType (argTypes argNames...) { ... }
    ^ BOOL (id object) { /* do something */ }
    
  • Block Pointer w/ assignment to a block literal:

    returnType (^blockPointerName) (argTypes...) = ^ returnType (argTypes argNames...) {
        // do something...
    };
    
    void (^myBlockPtr) (id) = ^(id object) { /* do something */ }
    
  • Function Pointer: (just for comparison)

    returnType (*functionPointerName) (arguments)
    

In any case, good typedefs are your friend. They will improve readability and clean up your method definitions. Life will be easier and I highly recommend making use of them as much as possible. Consider the difference between:

typedef BOOL (^SomeBlockType)(id object, NSUInteger idx, BOOL *stop);

- (void)collectionToCheck:(SomeBlockType)checkerBlock;
- (void)singleItemToCheck:(SomeBlockType)checkerBlock;

versus:

- (void)collectionToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)singleItemToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;

If you have to change the block signature, it is much easier to change the typedef. The compiler, being a nice fellow, will then tell you all the places the block signature doesn’t match.

Lastly, for block literals, you can abbreviate when you have void returnType or args:

^ (arguments) { ... }  // if returnType is void
^ returnType { ... }  // if argument is void
^ { ... }  // if returnType & arguments are void

It takes some getting used to. The compiler will attempt to help with cryptic error messages as well. If you see a good one, let me know and we’ll try to help decode it together.

Calling blocks

Calling a block works just like a calling a function.

void (^logBlock)(id) = ^ (id object) {
    NSLog(@"object %@", object);
};

logBlock(@"Hi");

But beware: trying to call a nil or undefined block will likely crash. If you are really unlucky, you might just corrupt some memory creating heisenbugs:

typedef void (^myBlockType)(id object);

myBlockType logBlock = nil;

logBlock(@"CRASH");

Instead, you need to check for nil or define a “do nothing” block:

typedef void (^myBlockType)(id object);

myBlockType logBlock = nil;

// check for nil
if (logBlock)
    logBlock();

// or define a do nothing block
logBlock = ^ (id object) { /* does nothing */ };

logBlock(@"DOES NOTHING");

Blocks and mutable variables

Blocks capture the variables in the surrounding scope, but they are treated as constants unless you use the __block keyword.

__block BOOL foundIt = NO;
BOOL foundIt2 = NO;         

[someArray enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop){
     if (obj == objectWeAreLookingFor) {
         *stop = YES;
         foundIt = YES;  //no compiler error
         foundIt2 = YES; //compiler MAD!
     }
 }];    

Blocks and memory management

Block are Objective-C objects, but their memory management situation is somewhat unique. Most of the time you won’t need to copy or retain a block at all. If you need to save a block beyond the scope in which it was created, you have two different options.

In C and C++, you use the Block_copy() and Block_release() functions to extend the life of a block beyond the scope in which it is created. In Objective C, you have the usual retain, copy, release and autorelease methods.

The nuance is that most of the time, in Objective C you want to use copy instead of retain. When blocks are created, like most variables, they live on the stack. When a copy is performed, the block is copied to the heap.

This can be easily done in a property with the copy keyword:

@property (nonatomic, copy) SomeBlockType someBlock;

The memory management of blocks changes slightly in ARC. In general, blocks just work. There are a few exceptions however.

When adding block pointers to a collection, you need to copy them first.

someBlockType someBlock = ^{NSLog(@"hi");};
[someArray addObject:[someBlock copy]];

Retain cycles are somewhat dangerous with blocks. You may have seen this warning:

warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles,4]

SomeBlockType someBlock = ^{
    [self someMethod];
};

The reason is that someBlock is strongly held by self and the block will “capture” and retain self when/if the block is copied to the heap.

The safer, but loquacious workaround is to use a weakSelf:

__weak SomeObjectClass *weakSelf = self;

SomeBlockType someBlock = ^{
    SomeObjectClass *strongSelf = weakSelf;
    if (strongSelf == nil) {
        // The original self doesn't exist anymore.
        // Ignore, notify or otherwise handle this case.
    } else {
        [strongSelf someMethod];
    }
};

Sometimes, you need to take care to avoid retain cycles with arbitrary objects: If someObject will ever strongly hold onto the block that uses someObject, you need weakSomeObject to break the cycle.

SomeObjectClass *someObject = ...
__weak SomeObjectClass *weakSomeObject = someObject;

someObject.completionHandler = ^{
    SomeObjectClass *strongSomeObject = weakSomeObject;
    if (strongSomeObject == nil) {
        // The original someObject doesn't exist anymore.
        // Ignore, notify or otherwise handle this case.
    } else {
        // okay, NOW we can do something with someObject
        [strongSomeObject someMethod];
    }
};

Many thanks to @jkubicek for reading early versions and providing feedback.



in lieu of comments, you should follow me on twitter at twitter/amattn and on twitch.tv at twitch.tv/amattn. I'm happy to chat about content here anytime.


the fine print:
aboutarchivemastodontwittertwitchconsulting or speaking inquiries
© matt nunogawa 2010 - 2023 / all rights reserved