Capturing automatic variables
You’ve learned about Block literals and Block-type variables, and should now understand the “anonymous function” part of “anonymous functions together with auto (local) variables.”
Next, you need to learn what the “together with automatic (local) variables” part means. For Blocks, this can be rephrased as “capturing the value of automatic variables.” The next example shows how this capture is accomplished.
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
In this source code, automatic variables “fmt” and “val” are declared and then used in the Block literal. Values of the automatic variables will be captured where the Block literal is written: the values will be stored when the Block literal is executed. Because the values have already been captured, even if they are modified after the Block literal is executed, values of the variables in the Block are never affected. In this source code, the automatic variables “fmt” and “val” are modified after the Block literal. The result is:
val = 10
It doesn’t display, “These values were changed. val = 2”. It displays the result with the values at the time the Block literal is executed. When it is executed, the automatic variable “fmt” has a pointer to “val = %d\n” and the “val” has integer value 10. These values are captured at that time and then used when the Block is executed.
That sums up capturing automatic variables. There is a specifier you should learn next. It is called the __block specifier. By using it, you can modify automatic variables without capturing them. The specifier is explained in the following section.
__block specifier
When automatic variables are captured, the values are read-only in the Block. The variables can’t be modified. Next the source code tries to assign 1 to the automatic variable “val”.
int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d\n", val);
In the source code, a value is assigned to the automatic variable, which is declared outside the Block literal. This causes a compile error as follows.
error: variable is not assignable (missing __block type specifier)
void (^blk)(void) = ^{val = 1;};
~~~ ^
When an automatic variable is declared outside a Block literal, you can use __block specifier to assign a value to the variable inside the Block. The following source code uses __block specifier for the automatic variable “val” so that the variable will be assignable inside the Block.
__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d\n", val);
The result is:
val = 1
By declaring the variable with __block specifier, you can assign values to the variable inside a Block. The __block-specified automatic variables are called “__block variables”.
Captured automatic variables
As described previously, if you assign values to a captured automatic variable, a compilation error occurs.
It causes the following compilation error.
error: variable is not assignable (missing __block type specifier)
void (^blk)(void) = ^{val = 1;};
~~~ ^
What happens when Objective-C objects are captured and a method is called to modify the object itself? Does it cause a compilation error as well?
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
No problem. An error will occur only if some value is assigned to the captured variable “array” itself. In this source code, the value of the captured variable is an object of the NS- MutableArray class. To rephrase it in C language terms, the value is a pointer to the instance of the struct for the NSMutableArray class object. If a value is assigned to the captured variable “array”, a compilation error will occur.
The following source code assigns a value to the captured automatic variable, so a compilation error occurs.
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
array = [[NSMutableArray alloc] init];
};
error: variable is not assignable (missing __block type specifier)
array = [[NSMutableArray alloc] init];
~~~~ ^
To do that, __block specifier is needed for the automatic variable.
__block id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
array = [[NSMutableArray alloc] init];
};
One more thing. When you use a C array, you have to use a pointer intentionally. Here is an example.
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
It uses an array, which has a C string literal. Nothing is assigned to the captured automatic variable. It looks to be no problem, but it causes a compilation error as follows.
error: cannot refer to declaration with an array type inside block
printf("%c\n", text[2]);
note: declared here
const char text[] = "hello";
^
That is because a C array can’t be captured by the current implementation of Blocks. You can avoid it by using a pointer as follows.
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};