More on blocks in Objective-C (but still without tears)

Feb 15, 2013 16:26 · 2363 words · 12 minute read iOS objectivec

This is the second part of a two-part look at blocks in Objective-C - you can find the first part here.

Blocks in detail

Apple provides some pretty detailed documentation on blocks with as complete a definition as I’ve ever seen – so let’s look at that and use it as a starting point.

According to Apple, a block is:

…an anonymous inline collection of code that:

  • Has a typed argument list just like a function

  • Has an inferred or declared return type

  • Can capture state from the lexical scope within which it’s defined

  • Can optionally modify the state of the lexical scope

  • Can share the potential for modification with other blocks defined within the same lexical scope

  • Can continue to share and modify state defined within the lexical scope (the stack frame) after the lexical scope (the stack frame) has been destroyed.

OK, let’s do this again in English.

Blocks have typed arguments

Just like functions and methods, you provide a list of arguments to a block. Unlike Objective-C methods where the parameters form part of the method name (for example:)

-(NSString *)doSomethingWithAString:(NSString *)theString andAnInt:(int)theInt;

With blocks, the arguments are treated in a much more C-like way:

(NSString *)(^myNewString)(NSString *theString, int theInt);

Blocks have a return type

Just as methods, blocks return things:

They can return nothing, in which case their return type is void:

void(^anIntishBLock)(int anInt, int anotherInt)

They can return something, in which case their return type is the type of whatever gets returned. (in this case, it’s a pointer to an NSString):

(NSString *)(^aStringyBlock)(NSString *foo, NSUInteger bar)

Blocks can capture state from the lexical scope in which it’s defined

OK, what does that mean??

You should already be comfortable with the idea that variables have scope. A variable that’s defined locally within a method only exists inside that method. If you want a variable to be accessible outside a method, then you have to define it as either an instance variable or a property.

For example, here’s two methods:

-(void)doFoo {
  NSString *foo = @”This is string foo”;
  // Do some exciting stuff with foo
}

-(void)doBar {
  NSString *bar = @”This is string foo”;
  // Do some exciting stuff with bar
}

The “region” where the variable exists is known as the “scope” – in these two methods, foo and bar exist in separate scopes.

This code wouldn’t work, because foo doesn’t exist within the scope of the doBar method:

-(void)doBar {
  NSString *bar = [NSString stringWithFormat:@”This is bar and %@”, foo];
  NSLog(@”%@”, bar);
}

Normally, that scope exists only for as long as the method is being executed. But blocks are special, in that they can capture the scope so that it doesn’t disappear. They close round the scope (which is why blocks are known as closures in some languages.)

Let’s look at an example of this. The UIView class comes with some really handy block-based animation features that you can use to animate various properties of the UIView subclasses that you create.

In one of my apps, I use the CoreLocation library to figure out where the user is using the iPhone’s GPS. That only works if the user has allowed the app to use Location Services, and it’s not much use if there’s no network available because the phone (and presumably the user!) are down a mine or in a tunnel.

When this happens, I display a warning to the user as an overlay that drops down from the top of the screen, hangs around for a few seconds, then slides back up again and disappears from view. This is done with UIView’s animateWithDuration:animations:completion: method.

The implementation looks like the code below (I’ve simplified it a bit here to save space). Don’t worry if you’ve not met this UIView method before – the details aren’t important. Just focus on what’s happening to the variables that are defined in the method:

-(void)displayLocationWarning {

  // Create a warning view to display
  UIView *warningView = [[UIView alloc] initWithFrame:self.offscreenFrame];

  // Configure the warning view with various properties -
  // background color, alpha value, warning message etc etc

  // Display the view by dropping it down from the top, then
  // make it disappear after 5 seconds by sliding back up again
  [UIView animateWithDuration:1.0 animations:^{

      [warningView setFrame:self.onscreenFrame];

  } completion:^(BOOL finished) {

      [UIView animateWithDuration:1.0 delay:5.0 options:UIViewAnimationCurveEaseInOut animations:^{

          [warningView setFrame:self.offscreenFrame];

      } completion:^(BOOL finished) {

          [warningView removeFromSuperview];

      }];

  }];

  NSLog(@”locationWarning displayed”);

  // remove the “find my location button” from the UI
  [self removeFindLocationButton];

}

Let’s step through this.

The first line creates the UIView that will display the warning, and then this is configured in the code that I’ve chopped out.

The warningView view is initially created with a frame property stored in the offscreenFrame property – this has an y value that’s negative, so warningView is tucked away above the top of the screen and isn’t visible.

Once warningView is created, it’s displayed by the animateWithDuration:animations:completion: block. That changes warningView’s frame to the values stored in self.onscreenFrame over a 1 second period – because self.onscreenFrame has a positive y value, the warningView appears to slide down from the top of the screen.

Once the animation has been performed, animateWithDuration:animations:completion: executes its completion: block, which reverses the process with another animation block (this one starting after a five second delay, and taking 1 second to run).

What’s interesting about all of this is that the animationWithDuration:animations:completion: block runs asynchronously – in other words, the NSLog and removeFindLocationButton methods are run while the animation is still going, and the displayLocationWarning method finishes well before the warningBlock has disappeared from the screen.

How can this be? warningBlock is defined locally within the displayLocationWarning method – its lexical scope, to use the formal term, is the method. As soon as the method ends, any variables defined within the method disappear. The lexical scope ends. Surely doing something to an object defined in a scope that’s gone will cause the program to crash?

This is the magic of blocks. When you pass a variable to a block – in this case the warningView object – the block “closes” around them. In effect, it ensures that the variable won’t disappear before the block has finished executing. The block makes a read-only (or immutable) copy of the variable and uses this to perform itself.

In effect, you don’t need to worry about how long the block will take to execute. Any variables that are passed into it will persist for as long as they need to.

Blocks can modify the lexical scope.

When a block closes round a variable, it makes an immutable copy – technically, it captures them as const variables. Trying to change the value of a const variable will cause the compiler to complain.

Here’s a simple example of something that won’t work:

int x = 123;

void(^printXandY)(int) = ^(int y) {
    x = x + y;
    NSLog(@"x plus y = %d", x);
};

printXandY(456);

If you try to build a project with this code, you’ll see a Variable is not assignable error.

Does this mean that we’re stuck with only being able to use immutable variables inside blocks? That seems very restrictive? What happens if the code needs to make changes to variables as it goes along?

The answer lies in the __block storage modifier (that’s the word block prefixed with two underscore characters). If you change this code to declare x as:

__block int x = 123;

Then the compiler will store in a special scope that’s guaranteed to survive for the lifetime of the block, and passes in a reference to the stored variable to the block itself.

This allows the block to change (or mutate) the value of x at will.

A subtle problem can occur here if you need to refer to self within the block – for example:

[UIView animateWithDuration:1.0 animations:^{

  [warningView setFrame:self.onscreenFrame];

} completion:^(BOOL finished) {

  [self methodToRunAfterAnimationCompletes];

}];

This would cause a compiler error (even when compiling under ARC) because the block will retain self, and this raises the possibility of a retain cycle.

To prevent this, you need to modify the way you pass in the reference to self:

__block __weak MySelfType *blockSafeSelf = self;

[UIView animateWithDuration:1.0 animations:^{

  [warningView setFrame:blockSafeSelf.onscreenFrame];

} completion:^(BOOL finished) {

  [blockSafeSelf methodToRunAfterAnimationCompletes];

}];

This creates a mutable weak reference to self, and will prevent any risk of a retain cycle.

Blocks can share the potential for modification with other blocks defined within the same lexical scope

Variables that are defined with the __block modifier can be shared between blocks created with the same scope. If you had a method that defines two more more blocks, then a variable defined with the __block modifier could be used by any or all of the blocks.

Using blocks in practice

One of the main uses for blocks in Objective-C is to allow abitrary chunks of code to be used to build up complex functionality. Here’s an example of a rather neat NSArray method called indexOfObjectPassingTest:

-(NSIndexSet)indexesOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate

This allows you to provide a block of code that defines what kind of conditions you want to test each element in the array against. For example, say you wanted to test an array of NSStrings to find the index of the first element containing a particular string:

NSUInteger index = [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
  NSString *stringUnderTest = (NSString *)obj;
  return ([stringUnderTest isEqualToString:testCondition]);
}];

What’s going on here?

The test is a block that returns a BOOL, and takes three parameters – an id called obj, an NSUInteger called idx, and a reference to a BOOL called stop.

idx is the index of the array element that’s going to be tested, while obj is the object itself. It’s an id so that the block can cope with any object that might be stored in the NSArray – in this instance we can safely assume that it will be an NSString, so it’s cast from id to NSString ready for the comparison to take place.

stop is a reference to a boolean value that’s used to control processing of the block – if the block returns YES, then the test has been passed and the index of that array will be returned.

The point of showing this example is that the predicate – the test used to determine which array element we’re after – can be any valid Objective-C code because it’s provided by in a block. The indexOfObjectPassingTest: method doesn’t care what that code is, so long as it returns a BOOL value. This is a very simple example, but the block could be hugely more complex depending on the requirement of your app.

Blocks as an alternative to delegate methods

Delegation is a design pattern where one object passes responsibility for performing some actions to another object. Although it’s very useful and used extensively within Objective-C, it’s also quite a heavyweight way of getting something to happen in response to a previous action.

You have to couple two objects together as delegator and delegate; and provide a protocol (formal or informal) so that the two objects know how to communicate. If what you’re trying to do is get one object to react to the actions of another, wouldn’t it be easier if you could get one object to perform both tasks?

This is where completion blocks come in. They’re used to provide a chunk of functionality that can remain undefined in a method declaration, but which gets provided by you when you implement the method. You’ve seen this above with UIView’s animateWithDuration:animations:completion: method. The completion block is the chunk of code that you want to be executed after the animations have been completed – it might be another piece of animation, or you might want to clean up some now-redundant objects, for example. The method declaration provides you with an empty slot for whatever code you need.

Instead of providing a delegate with an object that implements appropriate method, and tying the two together with a protocol, you simply use a block of code in-situ. Not only does that reduce the amount of code needed to implement the two objects and the protocol, it also has the benefit of keeping the functionality implemented by the block in the context in which it will be executed. This makes for much clearer and more readable code.

Blocks as an alternative to observers

The observer pattern is a design pattern that allows one object to register to be updated about notifications “broadcast” by another object. In Objective-C, this is done via NSNoticationCenter – objects create instances of the NSNotification class, and post these to the NSNotificationCenter. Other objects can register with NSNotificationCenter to be updated about the NSNotification objects that are posted.

It’s an example of “loose coupling” – a way of getting one object to react to the events occuring in a second, without the second object being aware of the existance of the first.

While this is very useful, the very fact that the coupling is loose distributes the responsibilities around your code. It’s not immediately obvious at first glance which objects will react to a notification being posted - not so much of a problem if it’s your code and you can remember going on, but more of an issue for your future self or if you’re dealing with code that someone else has written.

As with delegation, completion blocks provide the same capabilities, but without the drawbacks of obfuscating the functionality of your code.

Summary

Although the syntax of blocks can seem a bit unweildy at first, they provide a very powerful and flexible way of packaging up chunks of code into elements that can be used in a variety of ways. Not least of these powers is the ability to close around variables, which allows them to be used asynchronously after the scope in which the variable was defined has been destroyed.

An increasing number of class methods use blocks to exploit this power, as well as a cleaner alternative to observer or delegate patterns.

Further reading

Apple provide an extremely comprehensive guide to Objective-C blocks in the “Blocks Programming Topics” document available in the Developer Center.