67

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.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Chris Rutkowski
  • 1,774
  • 1
  • 26
  • 36

3 Answers3

132

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.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
Williham Totland
  • 28,471
  • 6
  • 52
  • 68
  • 4
    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 Feb 25 '14 at 12:21
  • However, is it possible to avoid the 'nil' termination thing? e.g. get the length of variable arguments? – karim Feb 25 '14 at 12:26
  • @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. – newacct Feb 26 '14 at 07:04
  • 1
    @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. – Williham Totland Feb 26 '14 at 15:45
  • @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 – jackal Feb 27 '14 at 15:50
23
- (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];
M-frankied
  • 360
  • 1
  • 5
12

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, @"");
}
karim
  • 15,408
  • 7
  • 58
  • 96