Reader beware: this post is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date and many contain code that no longer works or is superceded by newer APIs. Many others contain out-of-date information or offer advice and opinions I no longer endorse. Read "A new era for Cocoa with Love" for more.
This week I'll talk about methods that take variable numbers of arguments, also known as variadic methods. I'll show you the Objective-C syntax and implementation, give a quick rundown of the ways that Cocoa classes provide variable argument support and I'll also show you a way to fake va_list parameters to handle Cocoa's variadic method equivalents at runtime.
Variable arguments basics
Passing a variable number of arguments to a method is a convenient way to handle a list of variables that are in scope at compile time.
The Objective-C language handles variable arguments in the same way that Standard C does. Normally, you will encounter variable argument lists in one of two forms: "Format strings" or "Nil terminated lists".
The traditional example for variable arguments are format strings. Format strings contain a number of "placeholders" (escape sequences starting with a "%" sign) that are replaced with data from variables when the format string and the variable arguments are passed to the method.
This method is declared as follows:
The "..." is the variable argument.
This method declares a first parameter but everything else is "variable". This does not mean optional. Instead, it means that a number of extra parameters will be required. Exactly what number and their types depends on how the method works.
The documented behavior for
stringWithFormat:, is that it scans the
format string and requires 1 variable argument for every escape sequence (in this case there are 3) and the type must match the specifier for each escape sequence (in this case, "d", "@" and "g" specify
Nil terminated lists
The other common type of variable argument list method is one that takes a list of objects terminated by nil.
In Cocoa, these are commonly used for constructing collection objects.
The documented rule for this type of method is that the last argument must be
Getting argument numbers wrong
The details of "Format strings" and "Nil terminated lists" reveal the difficulty with variable argument lists in Objective-C: knowing how many arguments exist — i.e. when to stop reading arguments.
Objective-C, like C, does not have a runtime argument count, nor does it pass runtime argument types. So the number and types of arguments must be determined by some other policy. In the case of these two examples, the argument count is, respectively: the number of escape sequences in the "Format string" or the number of arguments before the first
nil. For "format strings" the types are determined by the escape sequences, for "Nil terminated" lists, the arguments are always pointers.
The penalty for getting variable argument list wrong can be severe: the compiler won't typically notice a problem and your code will crash or behave very strangely at runtime. The important lesson to learn is that you should only use variable arguments in a situation where the policy for numbers and types of arguments is very clear.
You can improve the compiler verification of variable argument lists by using the
-Wformatcompiler flag in GCC. See below for more about how this works.
Implementing variable arguments for your own methods
Lets look at the implementation for the example class:
Imagine we wanted a method that would set the
contents string this class contains by concatenating a variable argument list of strings together.
In this case, we will use the
nil terminated approach — the list of strings to concatenate will end with a
nil, so we know when to stop reading.
The declaration for the method looks like this:
NS_REQUIRES_NIL_TERMINATION part is a macro that tells the compiler that invocations of this method must include a
nil-terminated list of arguments. Failure to
nil-terminate the list will result in a compiler warning if
-Wformat is enabled.
-Wformat is not enabled by default. Double annoyingly, it is named "Typecheck Calls to printf/scanf" in the "GCC 4.0 Warnings" section of the "Build Settings" in Xcode, even though this setting affects the
NS_REQUIRES_NIL_TERMINATION macro which is used commonly throughout all of Cocoa — far beyond
The implementation of this method is as follows:
va_end are all standard C syntax for handling variable arguments. To describe them simply:
va_list- A pointer to a list of variable arguments.
va_start- Initializes a
va_listto point to the first argument after the argument specified.
va_arg- Fetches the next argument out of the list. You must specify the type of the argument (so that
va_argknows how many bytes to extract).
va_end- Releases any memory held by the
Generally speaking, you can use this
for loop for any variable argument situation where your arguments are all the same type. Other cases are a bit trickier but far less common — I'm sure you can work out how they would work if needed.
va_list in Cocoa
A number of classes in Cocoa have methods that take variable numbers of arguments. In most cases, these classes will also have an equivalent method that takes a
We can see an example of these
va_list equivalents by looking at
NSString declares the class method
stringWithFormat:... (which takes a variable number of arguments) and
NSString also declares the instance method
initWithFormat:arguments: (where the
arguments parameter is a
va_list) which handles the equivalent behavior of
va_list methods are used in the situation where your class defines a method with a variable argument list and you need to pass those variable arguments into the Cocoa method. For example, if the
StringContainer class listed above declared the method:
The implementation of this method would be as follows:
va_list parameter allows us to pass our own variable argument list to the Cocoa method so that the Cocoa method can handle the arguments.
Creating a fake va_list
va_list methods in Cocoa are helpful if you actually have a variable argument list.
There is another situation you many encounter though: you want to use a method like
-[NSString initWithFormat:arguments:] using a runtime generated array of arguments. There is no
NSString format method that takes an
NSArray of arguments, so how could we handle a format string at runtime?
The answer lies in how
va_list works. While GCC makes it very clear that
va_list is "platform specific", the reality is that on Mac and iPhone Objective-C platforms, it is simply a byte buffer containing the arguments. In fact, if you've ever used an ABI inspection tool (like class-dump-x) on a method taking a variable argument list, you'll see that it is simply a
To show how we can use this knowledge, consider the following method on the
If we assume that all of the variable arguments required fro the
formatString are objects, then we can implement this method as follows:
What I've done here is simply copied the
NSArray to a C-style byte buffer and then passed that buffer to the
If your arguments are not all Objective-C objects, you'd need to take greater care to assemble the byte buffer but the principles would be the same.
Some variadic notes
NSInvocation does not support variadic methods. I'm not sure why this is — maybe
NSInvocation wants to avoid making assumptions about
va_list, maybe it is because
NSMethodSignature can't describe the storage requirements of variable arguments for
NSInvocation or maybe the Cocoa developers have families and just wanted to go home early.
You can also write variadic macros, much like variadic methods, by using the
##__VA_ARGS__ placeholder in your macros. See the macros near the bottom of my post titled Supersequent Implementation for an example of how this works. As discussed briefly in that article, the version of GCC used for iPhone development handles variadic macros slightly differently, so pay attention to the differences when targetting the iPhone platform.
Variadic methods require a degree more care than regular methods because variable argument lists have no "introspection" (you cannot ask for the number and type of arguments) — so their usage relies on documentation and implicit agreements between the sender and receiver.
In your own code, variadic methods should be used sparingly — passing variables in an
NSDictionary is safer (if slightly slower and syntactically more verbose) due to the fact that these classes do offer introspection.
When the implicit sender/receiver agreement is clear, variadic methods work well. They certainly make creating instances of
NSDictionary easier and they are the only way to create a formatted
NSString in a single invocation.