In
this post, I start with the simplest C declarator and build in
complexity until we get to Objective-C blocks syntax. It took me a while
to get block syntax but once you understand how it is organized and
where it comes from, there is no looking in Google every time you need
to declare a block anymore.
If you want to be able to write block declarations from the top of your head, read-on!
I strongly advise against reading
this post in an RSS reader or read later service. It does heavy use of
colors to explain things and these colors would not appear there.
Declarators
Variables in C (and by extension Objective-C) are declared using declarators.
A declarator has 2 roles:
- Specify the type of the variable (what the compiler should expect to find in that memory space)
- Give that variable a name to make it available to the appropriate scope.
Let’s start with the most basic declarator:
int a;
This is most likely the first line of C code you have ever written.
int
is a basic type and a
is the variable name or identifier1.
To read a declarator, you start
from the identifier, then go right until you can’t and then you start
over from the left of the variable2 (we’ll explain why in the next section).
Here there is nothing to the right of our variable so it’s straightforward:
a
is an int
.
A declaration can only have 1 basic type and it’s the left most word of the declarator.
Declarators can modify basic types using modifiers to create derived types. The 4 modifiers (3 from ANSI-C and one from Apple’s proposed extension) are
*
, []
, ()
and ^
.The 3 ANSI-C modifiers
The pointer modifier *
int *a;
The basic type is still int and the name of the variable
a
. But the pointer modifier *
comes to tell us that a
is a pointer to an int
instead of an int
.
The
*
modifier is always to the left of the modified variable.
The array modifier []
int a[];
Here we see that the array modifier
[]
comes to tell us that a
is now an array of int
s instead of a simple int
. This can be completed by the dimension of the array e.g. int a[10];
.
The
[]
modifier is always to the right of the modified variable.
The function modifier ()
int f();
The function modifier
()
comes to tell us that f
is a function that returns an int
. This modifier can also specify the arguments that the function takes e.g. int f(long);
is a function that takes a long as an argument and returns an int.
The
()
modifier is always to the right of the modified variable.Composing modifiers
Pointers and arrays
Modifiers can be composed to
create more complex variable types. Similarly to how arithmetic
operations are ordered by precedence (* and / are executed before + and
–), modifiers are too.
[]
and ()
have higher precedence over *
(and ^
).
Since the 2 modifiers with higher precedence are written to the right of the variable, when reading complex declarator, you always start from the identifier and go right as long as possible then go left when you reach either the end of the declarator or a closing parenthesis.int *a[];
or as you can write it by adding parentheses to improve readability
int *(a[]);
is an array of pointers to an
int
.
But you might ask, what if I want to have a pointer to an array of ints? Well since
*
has lower precedence than []
, you need to use parenthesis to force that precedence.int (*a)[];
Here,
a
is a pointer to an array of int
s.Array and functions
You cannot have an array of functions and a function cannot return an array or a function3.
A function can however take an array as an argument.
int f(int [10]);
Here f is a function that takes an array of 10
int
s as an argument and returns an int
.Pointers and functions
int *f(); int *(f());
In both cases,
f
is a function that returns a pointer to an int
.
What if you want a pointer to a function? Parentheses!
int (*f)()
f
is a pointer to a function that returns an int
.
The block (or closure) pointer modifier ^
Apple introduced a 4th modifier in its proposed extension of the ANSI-C standard:
^
.
This modifier is called the block pointer modifier (or closure modifier
as they were originally called). Blocks are very similar to pointers for
functions. You declare a block the same way you would declare a pointer
to a function.
The block pointer modifier can only be applied to a function (you cannot write
int ^a;
as this is not defined).
This is the reason why
int ^b()
is illegal and will cause a compiler error: If you read this declarator
using the precedence rules, b would be a function that returns a block
pointer to an int. There is no such thing and this is why when declaring a block you need to always put the caret and the identifier in parentheses.int (^b)()
b
is a block pointer to a function that returns an int
or as abbreviated a block that returns an int
.
You can of course specify the arguments that the block takes:
int (^b)(long)
is a block that takes a long as an argument returns a
int
.
This is where the syntax for block declarations comes from.
Now you already know that there
are other syntaxes that you need to remember in order to use blocks: the
one used to define a block literal, and the one to pass the block to an
Objective-C method.
Abstract declarator
A declarator is made up of 2 things: an abstract declarator in which you insert the identifier.
Abstract declarators are used in 3 cases in standard C:
- In casts: in
int *a; long *b = (long *) a;
,(long *)
is an abstract declarator for a pointer to along
. - As arguments of sizeof():
malloc(sizeof(long *));
- When declaring argument types for a function:
int f(long *);
Objective-C uses abstract declarators in one more place: when declaring arguments or return values for methods.
|
Here both
long **
and int *
are abstract declarators.
So in order to use blocks as
arguments or return values for Objective-C methods, we need to find the
abstract declarator for these blocks. This is achieved by taking the
declarator and removing the identifier.
int (^b)()
becomes int (^)()
and int (^b)(long)
becomes int (^)(long)
.
example:
|
While you don’t have to name your
block’s arguments in these abstract declarators, it is a good idea to
do it. It will give a good hint as to what the block expects as argument
and Xcode autocomplete will make your life easier when using that
method.
Block literals
When you write
int a = 2;
, int a
is a declarator and 2
is a literal for an int.
The caret
^
is also used as a unary operator to transform a function implementation into a block4. You don’t need to specify the return type for the block (it is inferred from the return statement in that block) but you can.
Since this is the implementation of that block, you need to name your arguments here.
So for the block
int (^block)(long, long);
, a block literal would be:
|
Conclusion
As convoluted as it may seem,
blocks syntax in Objective-C is built upon standard C syntax. A block in
Objective-C is nothing more than a pointer to a function that captures
its scope. Once you understand that and practice writing and reading a
few blocks declaration, you’ll find it much easier to apprehend.
-
From ANSI-C draft page 35: “An identifier can denote an object; a function; a tag or a member of a structure, union, or enumeration; a typedef name; a label name; a macro name; or a macro parameter”
-
Oddly enough, Microsoft, of all companies, has a good page explaining the interpretation of complex declarators.
-
From ANSI-C draft page 133: “A function declarator shall not specify a return type that is a function type or an array type.
-
From Clang documentation: “A Block literal expression produces a reference to a Block. It is introduced by the use of the ^ token as a unary operator.
Sign up here with your email
ConversionConversion EmoticonEmoticon