How to create variable argument methods in Objective-C
Asked Answered
G

4

69

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.

Gonococcus answered 26/1, 2011 at 12:41 Comment(0)
M
134

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.

Muncey answered 26/1, 2011 at 12:56 Comment(5)
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.Selfforgetful
However, is it possible to avoid the 'nil' termination thing? e.g. get the length of variable arguments?Selfforgetful
@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.Josh
@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.Muncey
@karim, your note in first comment is right. For handling the first argument you should assign format to arg, handle it, then continue in loopContiguous
E
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];
Escalera answered 26/1, 2011 at 12:47 Comment(1)
ARC version: NSString *s=[[NSString alloc] initWithFormat:format arguments:args];Beetroot
S
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, @"");
}
Selfforgetful answered 25/2, 2014 at 12:25 Comment(0)
D
0

Here is GNUStep's implementation

#import <Foundation/Foundation.h>

#define    GS_MAX_OBJECTS_FROM_STACK    128

#define GS_USEIDLIST(firstObject, code...) ({\
  va_list    __ap; \
  unsigned int    __max = GS_MAX_OBJECTS_FROM_STACK; \
  unsigned int    __count = 0; \
  id        __buf[__max]; \
  id        *__objects = __buf; \
  id        __obj = firstObject; \
  va_start(__ap, firstObject); \
  while (__obj != nil && __count < __max) \
    { \
      __objects[__count] = __obj; \
      __obj = va_arg(__ap, id); \
      if (++__count == __max) \
    { \
      while (__obj != nil) \
        { \
          __count++; \
          __obj = va_arg(__ap, id); \
        } \
    } \
    } \
  va_end(__ap); \
  if (__count > __max) \
    { \
      unsigned int    __tmp; \
      __objects = (id*)NSZoneMalloc(NSDefaultMallocZone(),__count*sizeof(id)); \
      va_start(__ap, firstObject); \
      __objects[0] = firstObject; \
      for (__tmp = 1; __tmp < __count; __tmp++) \
    { \
      __objects[__tmp] = va_arg(__ap, id); \
    } \
      va_end(__ap); \
    } \
  code; \
  if (__objects != __buf) NSZoneFree (NSDefaultMallocZone(),__objects); \
})

@interface MyObject : NSObject
- (void)printWithObjects:(id)firstObject, ... NS_REQUIRES_NIL_TERMINATION;
@end

@implementation MyObject

- (void)printWithObjects:(id)firstObject, ... {
    GS_USEIDLIST(firstObject, {
        for (unsigned int i = 0; i < __count; i++) {
            NSLog(@"%@", __objects[i]);
        }
    });
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[MyObject new] printWithObjects:@"1", @"2", @"3", nil];
    }
    return 0;
}
Double answered 11/5 at 11:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.