Rainbow Coding

Introducing Practical Guide To Objective-C Blocks

Objective-C blocks can be extremely useful in certain cases.

Objective-C blocks are still fairly new feature to the language. They are not as simple as they could be, and they don't work on older iOS versions (only 4.0 and above). This is probably why you don't see them used that widely.

However, I don't care about old iOS versions. Blocks can be extremely useful in certain cases, which is why you probably want to master them.

This week I spent countless hours learning them by changing couple of core things in my game to support them, while seemingly getting them to work right away, I faced afterwards some complicated memory issues, so I decided to write a tutorial to explain how to use them.

1. Basic definition

Normally blocks in are defined as follows:

// definition
returnType (^nameOfBlock)(Parameter) =
                 returnType ^(Parameter param1) { code };
// call

If you have used C function pointers, you may notice blocks don't differ from them much syntactically. The only difference with them is actually ^ instead of * mark. (Of course, they are functionally quite different)

Here's a working example:

void (^print)(NSString *) =
              void ^(NSString *str) { NSLog(str); };

2. ALWAYS typedef

You probably noticed that the above looks very ugly and is tedious to write. However, there's a solution.

// This defines PrinterBlock as a pointer to block similar to the above.
typedef void (^PrinterBlock)(NSString*);

// Now you can use it like
PrinterBlock print = void ^(NSString *str) { NSLog(str); };

Please, do this always, for your own sake. Besides of improving readability, it also allows you to change the definition in just one place. You can create a header file import it in the files it's necessary.

3. Block's variables and scope

Block can see variables inside the current scope. This is one reason they are very useful, because you don't have to think what values to pass as parameters. However, they will also retain them, so it's a easy way to run into problems. Use with care! Also, blocks can not change variables unless you define them as __block.

int lineCounter = 0;
PrinterBlock print = void ^(NSString *str) {
    lineCounter++; // We can't change
    [Logger writeToFile:[NSString stringWithFormat:"%d: %@", lineCounter, str]]; // But we can read

// WORKS with __block . We want to change the lineCounter, so we must define the variable as __block
__block int lineCounter = 0;
PrinterBlock print = void ^(NSString *str) {
    [Logger writeToFile:[NSString stringWithFormat:"%d: %@", lineCounter, str]];

Note! self being retained is easy way to cause memory leaks! For example:

// BAD. Retains self and the side-effect is that when your object is released, it won't go into dealloc
// because the block has it retained
PrinterBlock print = void ^(NSString *str) { [self writeToFile:str]; };

// GOOD. Passes the pointer as special block variable
__block FileWriter *safeSelf = self;
PrinterBlock print = void ^(NSString *str) { [safeSelf writeToFile:str]; };

So, never use self inside the block!

4. Blocks as method parameters

At some point, you probably want to pass a block as a parameter to method. The syntax goes as follows, but this is a good reason to use typedefs

// normal
-(void) setPrinterBlock: (void ^(NSString*)) param
// typedefs
-(void) setPrinterBlock: (PrinterBlock) param

Also, Objective-C programming guidelines state that block should always be the last argument. This is purely a readability issue, because imagine the following:

// BAD.
-(void) doSomethingWithStr:(NSString *)str integer:(int)i block:(void ^(NSString*))block anotherInt:(int)j anotherStr:(NSString *)str2;

// when you call such method, it becomes
[self doSomethingWithStr:str integer:i block:[[^(NSString *str) {
            // do something with str
            // and do something more
            // until this is 10 lines long
} copy] autorelease] anotherInt:j anotherStr:str2];

As you can see, the last two variables after the block are difficult to notice.

5. Memory management

This is probably the most difficult thing about blocks. Essentially they are like all normal Objective-C objects, but there are some things to keep in mind.

Blocks are always created to stack. This means that they disappear when out of scope. This is why, when you want to save your block for later use, you have to copy it. When you copy the block, it is copied to heap.

There are couple of ways for handling this properly.

[^(){} copy];
[[^(){} copy] autorelease];

What you should use depends on what you are doing. Since this is a simple guide, I won't go into details here. Instead, I recommend storing blocks with @property:

//inside class interface
PrinterBlock printerBlock;

// the property line
@property (copy) PrinterBlock printerBlock;

// inside @implementation
@synthesize printerBlock;

// inside some method. HOX: use the self, because otherwise it won't be copied!!!
self.printerBlock = ^(NSString *str) { NSLog(str); }

// inside dealloc
[printerBlock release];

Okay, I mastered blocks, but what's the point?

Are they complicated? Probably. Are they necessary? No. Are they cool and useful? Yes. Can they make code easier to understand and more efficient to write? Yes, absolutely!

Personally I think they make code much easier to read. You can define simple action related to object at the same scope the object is created, instead of defining a separate method. Another reason is that you don't have to pass tons of variables, since it has read-only access and can have read-write the variables in the scope it's created in. Third reason is that they are flexible to change since they don't require separate methods and protocols.

Whether to use them or not depends on what you are doing. I'm using them for smallish updater methods, animations, observer patterns, etc and find them very good for that. Yet, I probably wouldn't use them for everything, especially very complex things.

© RainbowCoding 2010-2023