How to create variable argument methods in Objective-C

43,771

Solution 1

What these are called, generally, is "variadic functions" (or methods, as it were).

To create this, simply end your method declartion with , ..., as in

- (void)logMessage:(NSString *)message, ...;

At this point you probably want to wrap it in a printf-like function, as implementing one of those from scratch is trying, at best.

- (void)logMessage:(NSString *)format, ... {
  va_list args;
  va_start(args, format);
  NSLogv(format, args);
  va_end(args);
}

Note the use of NSLogv and not NSLog; consider NSLog(NSString *, ...); vs NSLogv(NSString *, va_list);, or if you want a string; initWithFormat:arguments: on NSString *.


If, on the other hand, you are not working with strings, but rather something like

+ (NSArray *)arrayWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;

things get a lot easier.

In that case, instead of a vprintf-style function, use a loop going through args, assuming id as you go, and parse them as you would in any loop.

- (void)logMessage:(NSString *)format, ... {
  va_list args;
  va_start(args, format);

  id arg = nil;
  while ((arg = va_arg(args,id))) {
  /// Do your thing with arg here
  }

  va_end(args);
}

This last sample, of course, assumes that the va_args list is nil-terminated.

Note: In order to make this work you might have to include <stdarg.h>; but if memory serves, this gets included in connection with NSLogv, meaning it comes down by way of "Foundation.h", therefore also "AppKit.h" and "Cocoa.h", as well as a number of others; so this should work out of the box.

Solution 2

- (void)methodWithFormat:(NSString*)format, ... {
  va_list args;
  va_start(args,format);
  //loop, get every next arg by calling va_arg(args,<type>)
  // e.g. NSString *arg=va_arg(args,NSString*) or int arg=(args,int)
  va_end(args);
}

If you want to pass the variable arguments to stringWithFormat:, use something like:

NSString *s=[[[NSString alloc] initWithFormat:format arguments:args] autorelease];

Solution 3

One thing to mention here is that, the first NSString parameter here comes as format, and the other are passed in the variable argument. right? So before entering the for loop, you have one parameter to handle.

- (NSString *) append:(NSString *)list, ...
{
    NSMutableString * res = [NSMutableString string];
    [res appendString:list];

    va_list args;
    va_start(args, list);
    id arg = nil;

    while(( arg = va_arg(args, id))){
        [res appendString:arg];
    }
    va_end(args);
    return res;
}

- (void) test_va_arg
{
    NSString * t = [self append:@"a", @"b", @"c", nil];
    STAssertEqualObjects(@"abc", t, @"");
}
Share:
43,771
Chris Rutkowski
Author by

Chris Rutkowski

Updated on July 05, 2022

Comments

  • Chris Rutkowski
    Chris Rutkowski almost 2 years

    Maybe this will be obviously simple for most of you, but could you please give an example how to create similar methods (in Objective-C) and functions in C to create functions like NSString's stringWithFormat:, or NSLog().

    Just to remind:

    [NSString stringWithFormat:@"example tekst %i %@ %.2f", 122, @"sth", 3.1415"];
    NSLog(@"account ID %i email %@", accountID, email);
    

    I'd like to create the similar to NSString's method stringWithFormat:, NSURL - urlWithFormat.

  • karim
    karim over 10 years
    One thing to mention here is that, the first NSString parameter here comes as format, and the other are passed in the variable argument. right? So before entering the for loop, you have one parameter to handle.
  • karim
    karim over 10 years
    However, is it possible to avoid the 'nil' termination thing? e.g. get the length of variable arguments?
  • newacct
    newacct over 10 years
    @karim: It is not possible with C varargs to know the number and types of the arguments. The called function has to somehow know the types and when to stop.
  • Williham Totland
    Williham Totland over 10 years
    @karim: Depending on your use case, you could instead give it a signature of doSomethingWith:(size_t) objects:(id), ...; this is in essence what stringWithFormat: does (except that the number of expected arguments is obtained by dissecting the format string). Generally, tho', I'd advise against it, and in favor of nil termination, as the requirement to keep the count up to date could easily introduce defects.
  • jackal
    jackal over 10 years
    @karim, your note in first comment is right. For handling the first argument you should assign format to arg, handle it, then continue in loop
  • godzilla
    godzilla over 9 years
    ARC version: NSString *s=[[NSString alloc] initWithFormat:format arguments:args];