Advertisement
iOS SDK

Objective-C Succinctly: Blocks

by

Blocks are actually an extension to the C programming language, but they are heavily utilized by Apple's Objective-C frameworks. They are similar to C#'s lambdas in that they let you define a block of statements inline and pass it around to other functions as if it were an object.

Figure 36 Processing data with functions vs performing arbitrary actions with blocks

Processing data with functions vs. performing arbitrary actions with blocks

Blocks are incredibly convenient for defining callback methods since they let you define the desired functionality at the point of invocation rather than somewhere else in your program. In addition, blocks are implemented as closures (just like lambdas in C#), which makes it possible to capture the local state surrounding the block without any extra work.


Creating Blocks

Block syntax can be a little unsettling compared to the Objective-C syntax we've been using throughout this book, so don't worry if it takes a while to be comfortable with them. We'll start by looking at a simple example:

^(int x) {
	return x * 2;
};

This defines a block that takes an integer parameter, x, and returns that value multiplied by two. Aside from the caret (^), this resembles a normal function: it has a parameter list in parentheses, an instruction block enclosed in curly braces, and an (optional) return value. In C#, this is written as:

x => x * 2;

But, blocks aren't limited to simple expressions-they can contain an arbitrary number of statements, just like a function. For example, you can add an NSLog() call before returning a value:

^(int x) {
	NSLog(@"About to multiply %i by 2.", x);
	return x * 2;
};

Parameter-Less Blocks

If your block doesn't take any parameters, you can omit the parameter list altogether:

^ {
	NSLog(@"This is a pretty contrived block.");
	NSLog(@"It just outputs these two messages.");
};

Using Blocks as Callbacks

On its own, a block isn't all that useful. Typically, you'll pass them to another method as a callback function. This is a very powerful language feature, as it lets you treat functionality as a parameter, rather than being limited to data. You can pass a block to a method as you would any other literal value:

[anObject doSomethingWithBlock:^(int x) {
	NSLog(@"Multiplying %i by two");
	return x * 2;
}];

The doSomethingWithBlock: implementation can run the block just like it would run a function, which opens the door to a lot of new organizational paradigms.

As a more practical example, let's take a look at the sortUsingComparator: method defined by NSMutableArray. This provides the exact same functionality as the sortedArrayUsingFunction: method we used in the Data Types chapter, except you define the sort algorithm in a block instead of a full-fledged function:

Included code sample: SortUsingBlock

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
	@autoreleasepool {

		NSMutableArray *numbers = [NSMutableArray arrayWithObjects:
								   [NSNumber numberWithFloat:3.0f],
								   [NSNumber numberWithFloat:5.5f],
								   [NSNumber numberWithFloat:1.0f],
								   [NSNumber numberWithFloat:12.2f], nil];

		[numbers
		 sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
			float number1 = [obj1 floatValue];
			float number2 = [obj2 floatValue];
			if (number1 < number2) {
				return NSOrderedAscending;
			} else if (number1 > number2) {
				return NSOrderedDescending;
			} else {
				return NSOrderedSame;
			}
		}];

		for (int i=0; i<[numbers count]; i++) {
			NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]);
		}

	}
	return 0;
}

Again, this is a straightforward ascending sort, but being able to define the sort algorithm in the same place as the function invocation is more intuitive than having to define an independent function elsewhere in the program. Also notice that you can declare local variables in a block just as you would in a function.

The standard Objective-C frameworks use this design pattern for everything from sorting, to enumeration, to animation. In fact, you could even replace the for-loop in the last example with NSArray's enumerateObjectsUsingBlock: method, as shown here:

[sortedNumbers
 enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
	NSLog(@"%lu: %0.1f", idx, [obj floatValue]);
	if (idx == 2) {
		// Stop enumerating at the end of this iteration.
		*stop = YES;
	}
}];

The obj parameter is the current object, idx is the current index, and *stop is a way to exit the enumeration prematurely. Setting the *stop pointer to YES tells the method to stop enumerating after the current iteration. All of this behavior is specified by the enumerateObjectsUsingBlock: method.

While animation is a bit off-topic for this book, it's still worth a brief explanation to help understand the utility of blocks. UIView is one of the most used classes in iOS programming. It's a generic graphical container that lets you animate its contents via the animateWithDuration:animations: method. The second parameter is a block that defines the final state of the animation, and the method automatically figures out how to animate the properties using the first parameter. This is an elegant, user-friendly way to define transitions and other timer-based behavior. We'll discuss animations in much more detail in the upcoming iOS Succinctly book.


Storing and Executing Blocks

Aside from passing them to methods, blocks can also be stored in variables for later use. This use case essentially serves as an alternative way to define functions:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
	@autoreleasepool {

		int (^addIntegers)(int, int);

		addIntegers = ^(int x, int y) {
			return x + y;
		};

		int result = addIntegers(24, 18);
		NSLog(@"%i", result);

	}
	return 0;
}

First, let's inspect the syntax for declaring block variables: int (^addIntegers)(int, int). The name of this variable is simply addIntegers (without the caret). This can be confusing if you haven't been using blocks very long. It helps to think of the caret as the block's version of the dereference operator (*). For example, a pointer called addIntegers would be declared as *addIntegers-likewise, a block of the same name is declared as ^addIntegers. However, keep in mind that this is merely a superficial similarity.

In addition to the variable name, you also need to declare all of the metadata associated with the block: the number of parameters, their types, and the return type. This enables the compiler to enforce type safety with block variables. Note that the caret is not part of the variable name-it's only required in the declaration.

Next, we use the standard assignment operator (=) to store a block in the variable. Of course, the block's parameters ((int x, int y)) must match parameter types declared by the variable ((int, int)). A semicolon is also required after the block definition, just like a normal variable assignment. Once it has been populated with a value, the variable can be called just like a function: addIntegers(24, 18).

Parameter-Less Block Variables

If your block doesn't take any parameters, you must explicitly declare this in the variable by placing void in the parameter list:

void (^contrived)(void) = ^ {
	NSLog(@"This is a pretty contrived block.");
	NSLog(@"It just outputs these two messages.");
};

contrived();

Working With Variables

Variables inside of blocks behave in much the same way as they do in normal functions. You can create local variables within the block, access parameters passed to the block, and use global variables and functions (e.g., NSLog()). But, blocks also have access to non-local variables, which are variables from the enclosing lexical scope.

int initialValue = 32;
int (^addToInitialValue)(int) = ^(int x) {
	return initialValue + x;
};

NSLog(@"%i", addToInitialValue(10)); // 42

In this case, initialValue is considered a non-local variable within the block because it is defined outside of the block (not locally, relative to the block). Of course, the fact that non-local variables are read-only implies that you cannot assign to them:

int initialValue = 32;
int (^addToInitialValue)(int) = ^(int x) {
	initialValue = 5; // This will throw a compiler error.
	return initialValue + x;
};

Having access to the surrounding (non-local) variables is a big deal when using inline blocks as method parameters. It provides a convenient way to represent any state required within the block.

For example, if you were animating the color of a UI component and the target color was calculated and stored in a local variable before the block definition, you could simply use the local variable within the block-no extra work required. If you didn't have access to non-local variables, you would have passed the color value as an additional block parameter. When your callback functionality relies on a large portion of the surrounding state, this can be very cumbersome.

Blocks Are Closures

However, blocks don't just have access to non-local variables-they also ensure that those variables will never change, no matter when or where the block is executed. In most programming languages, this is called a closure.

Closures work by making a constant, read-only copy of any non-local variables and storing them in a reference table with the statements that make up the block itself. These read-only values are used every time the block is executed, which means that even if the original non-local variable changes, the value used by the block is guaranteed to be the same as it was when the block was defined.

Figure 37 Storing non-local variables in a reference table

Storing non-local variables in a reference table

We can see this in action by assigning a new value to the initialValue variable from the previous example:

int initialValue = 32;
int (^addToInitialValue)(int) = ^(int x) {
	return initialValue + x;
};

NSLog(@"%i", addToInitialValue(10)); // 42

initialValue = 100;
NSLog(@"%i", addToInitialValue(10)); // Still 42.

No matter where you call addToInitialValue(), the initialValue used by the block will always be 32, because that's what it was when it was created. For all intents and purposes, it's as though the initialValue variable was transformed into a literal value inside of the block.

So, the utility of blocks is two-fold:

  1. They allow you to represent functionality as an object.
  2. They let you represent state information alongside that functionality.

The whole idea behind encapsulating functionality in a block is to be able to use it later in the program. Closures make it possible to ensure predictable behavior whenever a block is executed by freezing the surrounding state. This makes them an integral aspect of block programming.

Mutable Block Variables

For most cases, capturing state with closures is intuitively what you would expect from a block. There are, however, times that call for the opposite behavior. Mutable block variables are non-local variables that are designated read-write instead of the default read-only. To make a non-local variable mutable, you have to declare it with the __block modifier, which creates a direct link between the variable used outside the block and the one used inside of the block. This opens the door to using blocks as iterators, generators, and any other kind of object that processes state.

Figure 38 Creating a direct link with a mutable block variable

Creating a direct link with a mutable block variable

The following example shows you how to make a non-local variable mutable:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
	@autoreleasepool {

		__block NSString *name = @"Dave";

		void (^generateRandomName)(void) = ^ {
			NSLog(@"Changing %@ to Frank", name);
			name = @"Frank";
		};

		NSLog(@"%@", name);        // Dave

		// Change it from inside the block.
		generateRandomName();      // Changing Dave to Frank.
		NSLog(@"%@", name);        // Frank

		// Change it from outside the block.
		name = @"Heywood";
		generateRandomName();      // Changing Heywood to Frank.

	}
	return 0;
}

This looks almost exactly the same as the previous example, with two very significant differences. First, the non-local name variable can be assigned from within the block. Second, changing the variable outside of the block does update the value used within the block. It's even possible to create multiple blocks that all manipulate the same non-local variable.

The only caveat to using the __block modifier is that it cannot be used on variable-length arrays.

Defining Methods That Accept Blocks

Arguably, creating methods that accept blocks is more useful than storing them in local variables. It gives you the opportunity to add your own enumerateObjectsUsingBlock:-style methods to custom classes.

Consider the following interface for the Person class:

// Person.h
@interface Person : NSObject

@property int age;

- (void)celebrateBirthdayWithBlock:(void (^)(int))activity;

@end

The void (^)(int) code is the data type for the block that you want to accept. In this case, we'll be accepting a block with no return value and a single integer parameter. Notice that, unlike block variables, this doesn't require a name for the block-just an unadorned ^ character.

You now have all the skills necessary to create methods that accept blocks as parameters. A simple implementation for the Person interface shown in the previous example might look something like:

// Person.m
#import "Person.h"

@implementation Person

@synthesize age = _age;

- (void)celebrateBirthdayWithBlock:(void (^)(int))activity {
	NSLog(@"It's a party!!!");
	activity(self.age);
}

@end

Then, you can pass a customizable activity to perform on a Person's birthday like so:

// main.m
int main(int argc, const char * argv[]) {
	@autoreleasepool {

		Person *dave = [[Person alloc] init];
		dave.age = 37;

		[dave celebrateBirthdayWithBlock:^(int age) {
			NSLog(@"Woot! I'm turning %i", age + 1);
		}];

	}
	return 0;
}

It should be readily apparent that using blocks as parameters is infinitely more flexible than the standard data types we've been using up until this chapter. You can actually tell an instance to do something, rather than merely process data.


Summary

Blocks let you represent statements as Objective-C objects, which enables you to pass arbitrary actions to a function instead of being limited to data. This is useful for everything from iterating over a sequence of objects to animating UI components. Blocks are a versatile extension to the C programming language, and they are a necessary tool if you're planning to do a lot of work with the standard iOS frameworks. In this chapter, we learned how to create, store, and execute blocks, and we learned about the intricacies of closures and the __block storage modifier. We also discussed some common usage paradigms for blocks.

Thus concludes our journey through Objective-C. We've covered everything from basic syntax to core data types, classes, protocols, properties, methods, memory management, error handling, and even the advanced use of blocks. We focused more on language features than creating graphical applications, but this provided a solid foundation for iOS app development. By now, I hope you're feeling very comfortable with the Objective-C language.

Remember that Objective-C relies on many of the same object-oriented concepts as other OOP languages. While we only touched on a few object-oriented design patterns in this book, virtually all of the organizational paradigms available to other languages are also possible in Objective-C. This means that you can easily leverage your existing object-oriented knowledge base with the tools presented in the preceding chapters.


iOS Succinctly

If you're ready to start building functional iPhone and iPad applications, be sure to check out the second part of this series, iOS Succinctly. This hands-on guide to app development applies all of the Objective-C skills acquired from this book to real-world development situations. We'll walk through all of the major Objective-C frameworks and learn how to perform tasks along the way, including: configuring user interfaces, capturing input, drawing graphics, saving and loading files, and much, much more.

This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Related Posts
  • Code
    Mobile Development
    C++ Succinctly: Pointers, References, and Const-CorrectnessPreview image@2x
    Even though the concept of a pointer is simple, many people struggle with this topic. A pointer is nothing more than a variable that holds a memory address. After reading this article, pointers and references should no longer have secrets for you.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Exceptions and Errors0e5ds8 preview image@2x
    In Objective-C, there are two types of errors that can occur while a program is running. Unexpected errors are "serious" programming errors that typically cause your program to exit prematurely. These are called exceptions, since they represent an exceptional condition in your program. On the other hand, expected errors occur naturally in the course of a program's execution and can be used to determine the success of an operation. These are referred to as errors.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Categories and Extensions0e5ds8 preview image@2x
    Categories are an Objective-C language feature that let you add new methods to an existing class, much like C# extensions. However, do not confuse C# extensions with Objective-C extensions. Objective-C's extensions are a special case of categories that let you define methods that must be declared in the main implementation block.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Methods0e5ds8 preview image@2x
    In this chapter, we'll explore Objective-C methods in much more detail than we have in previous chapters. This includes an in-depth discussion of instance methods, class methods, important built-in methods, inheritance, naming conventions, and common design patterns.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Memory Management0e5ds8 preview image@2x
    Memory must be allocated for each object your application uses, and it must be deallocated when your application is done with it to ensure your application is using memory as efficiently as possible. It's important to understand Objective-C's memory management environment to ensure your program doesn't leak memory or try to reference objects that no longer exist.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Data Types0e5ds8 preview image@2x
    Objective-C has two categories of data types. First, remember that Objective-C is a superset of C, so you have access to all of the native C data types like char, int, float, etc. Objective-C also defines a few of its own low-level types, including a Boolean type. Let's call all of these "primitive data types."Read More…