Block objects can either be inline or coded as independent blocks of
code. Let’s start with the latter type. Suppose you have a method in
Objective-C that accepts two integer values of type NSInteger
and returns the difference of the two
values, by subtracting one from the other, as an NSInteger
:
- (NSInteger) subtract:(NSInteger)paramValue from:(NSInteger)paramFrom{ return paramFrom - paramValue; }
That was very simple, wasn’t it? Now let’s translate this Objective-C code to a pure C function that provides the same functionality to get one step closer to learning the syntax of block objects:
NSInteger subtract(NSInteger paramValue, NSInteger paramFrom){ return paramFrom - paramValue; }
You can see that the C function is quite different in syntax from its Objective-C counterpart. Now let’s have a look at how we could code the same function as a block object:
NSInteger (^subtract)(NSInteger, NSInteger) = ^(NSInteger paramValue, NSInteger paramFrom){ return paramFrom - paramValue; };
Before I go into details about the syntax of block objects, let me
show you a few more examples. Suppose we have a function in C that takes a
parameter of type NSUInteger
(an
unsigned integer) and returns it as a string of type NSString
. Here is how we implement this in
C:
NSString* intToString (NSUInteger paramInteger){ return [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger]; }
To learn about formatting strings with system-independent format specifiers in Objective-C, please refer to String Programming Guide, iOS Developer Library on Apple’s website.
The block object equivalent of this C function is shown in Example 1-1.
Example 1-1. Example block object defined as function
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){ NSString *result = [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger]; return result; };
The simplest form of an independent block object would be a block
object that returns void
and does not
take any parameters in:
void (^simpleBlock)(void) = ^{ /* Implement the block object here */ };
Block objects can be invoked in the exact same way as C functions. If they have any parameters, you pass the parameters to them like a C function and any return value can be retrieved exactly as you would retrieve a C function’s return value. Here is an example:
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){ NSString *result = [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger]; return result; }; - (void) callIntToString{ NSString *string = intToString(10); NSLog(@"string = %@", string); }
The callIntToString
Objective-C
method is calling the intToString
block
object by passing the value 10 as the only parameter to this block object
and placing the return value of this block object in the string
local variable.
Now that we know how to write block objects as independent blocks of code, let’s have a look at passing block objects as parameters to Objective-C methods. We will have to think a bit abstractly to understand the goal of the following example.
Suppose we have an Objective-C method that accepts an integer and performs some kind of transformation on it, which may change depending on what else is happening in our program. We know that we’ll have an integer as input and a string as output, but we’ll leave the exact transformation up to a block object that can be different each time our method runs. This method, therefore, will accept as parameters both the integer to be transformed and the block that will transform it.
For our block object, we’ll use the same intToString
block object that we implemented
earlier in Example 1-1. Now we need an
Objective-C method that will accept an unsigned integer parameter and a
block object as its parameter. The unsigned integer parameter is easy, but
how do we tell our method that it has to accept a block object
of the same type as the intToString
block object? First we typedef
the signature of the intToString
block object, which tells the
compiler what parameters our block object should accept:
typedef NSString* (^IntToStringConverter)(NSUInteger paramInteger);
This typedef
just tells the
compiler that block objects that accept an integer parameter and return a
string can simply be represented by an identifier named IntToStringConverter
. Now let’s go ahead and
write our Objective-C method that accepts both an integer and a block
object of type IntToStringConverter
:
- (NSString *) convertIntToString:(NSUInteger)paramInteger usingBlockObject:(IntToStringConverter)paramBlockObject{ return paramBlockObject(paramInteger); }
All we have to do now is call the convertIntToString:
method with our block object
of choice (Example 1-2).
Example 1-2. Calling the block object in another method
- (void) doTheConversion{ NSString *result = [self convertIntToString:123 usingBlockObject:intToString]; NSLog(@"result = %@", result); }
Now that we know something about independent block objects, let’s
turn to inline block objects. In the doTheConversion
method we just saw, we passed
the intToString
block object as the
parameter to the convertIntToString:usingBlockObject:
method.
What if we didn’t have a block object ready to be passed to this method?
Well, that wouldn’t be a problem. As mentioned before, block objects are
first-class functions and can be constructed at runtime. Let’s have a look
at an alternative implementation of the doTheConversion
method (Example 1-3).
Example 1-3. Example block object defined as function
- (void) doTheConversion{ IntToStringConverter inlineConverter = ^(NSUInteger paramInteger){ NSString *result = [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger]; return result; }; NSString *result = [self convertIntToString:123 usingBlockObject:inlineConverter]; NSLog(@"result = %@", result); }
Compare Example 1-3 to the earlier
Example 1-1. I have removed the initial code that
provided the block object’s signature, which consisted of a name and
argument, (^intToString)(NSUInteger)
. I
left all the rest of the block object intact. It is now an anonymous
object. But this doesn’t mean I have no way to refer to the block object.
I assign it using an equal sign to a type and a name: IntToStringConverter inlineConverter
. Now I can
use the data type to enforce proper use in methods, and use the name to
actually pass the block object.
In addition to constructing block objects inline as just shown, we can construct a block object while passing it as a parameter:
- (void) doTheConversion{ NSString *result = [self convertIntToString:123 usingBlockObject:^NSString *(NSUInteger paramInteger) { NSString *result = [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger]; return result; }]; NSLog(@"result = %@", result); }
Compare this example with Example 1-2.
Both methods use a block object through the usingBlockObject
syntax. But whereas the earlier
version referred to a previously declared block object by name (intToString
), this one simply creates a block
object on the fly. In this code, we constructed an inline block object
that gets passed to the convertIntToString:usingBlockObject:
method as
the second parameter.
I believe that at this point you know enough about block objects to be able to move to more interesting details, which we’ll begin with in the following section.