views:

2167

answers:

6

Hi, in python is easy build a dictionary or array and pass it unpacked to a function with variable parameters

I have this:

- (BOOL) executeUpdate:(NSString*)sql, ... {

And the manual way is do this:

[db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  @"hi'", // look!  I put in a ', and I'm not escaping it!
  [NSString stringWithFormat:@"number %d", i],
  [NSNumber numberWithInt:i],
  [NSDate date],
  [NSNumber numberWithFloat:2.2f]];

But I can't hardcode the parameters where I calling, I want:

NSMutableArray *values = [NSMutableArray array];

for (NSString *fieldName in props) {
  ..
  ..
  [values addObject : value]
}
[db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,??values];
A: 

To accomplish what you want, you have to use "varargs", as your method uses, or you can pass in an array of values, something like [db executeUpdate:sql withValues:vals];, and then pull out the values in the method. But there's no way to do something more "Pythonic", such as automatically unpacking a tuple of values, á la def executeUpdate(sql, *args).

mipadi
+1  A: 

Unfortunately (Objective-)C doesn't provide a way to do that. The executeUpdate method would need to accept an NSArray instead of variable argument list in this case.

However, if you do know the amount of entries in the array (you have the amount in the string in the example anyway), you can of course do something like

[db executeUpdate:@"insert into test (a, b) values (?, ?)", [values objectAtIndex:0], [values objectAtIndex:1]]

If executeUpdate is an external library method and that library does not offer a version of the method accepting an NSArray, you could come up with your own wrapper function. The function would take the query string and an array as argument. This function would then call the executeUpdate method with correct amount of arguments based on the length of the array, something along the lines of

if ([values count] == 1) {
  [db executeUpdate:query, [values objectAtIndex:0]];
}
else if ([values count] == 2) {
  [db executeUpdate:query, [values objectAtIndex:0], [values objectAtIndex:1]];
}

you could then call this new function as

executeUpdateWrapper(@"insert into test (a, b) values (?, ?)", values);

The obvious drawback in this solution is that you need to handle all possible lengths of the array separately in the function and it has a lot of copy-paste code.

Timo Metsälä
+6  A: 

Unfortunately, no. Objective-C doesn't have argument unpacking like you get in a lot of modern languages. There isn't even a good way to work around it that I've ever found.

Part of the problem is that Objective-C is essentially just C. It does multiple argument passing with C varargs, and there's no simple way to do this with varargs. A relevant SO discussion.

Chuck
I just found out from another post that you can create this functionality as needed if desired, although Cocoa doesn't have methods like this by default. http://stackoverflow.com/questions/1058736/#1061750
Quinn Taylor
It's been a while since I investigated this, but I think that method for creating arbitrary varargs lists is somewhat fragile. It depends on implementation details that aren't guaranteed. Just a warning.
Chuck
+4  A: 

I wanted to do the same thing. I came up with the following, which works fine, given some constraints on the input variables.

NSArray* VarArgs(va_list ap)
{
  id obj;
  NSMutableArray* array = [NSMutableArray array];

  while ((obj = va_arg(ap, id))) {
    [array addObject:obj];
  }
  return array;
}

#define VarArgs2(_last_) ({ \
  va_list ap; \
  va_start(ap, _last_); \
  NSArray* __args = VarArgs(ap); \
  va_end(ap); \
  if (([__args count] == 1) && ([[__args objectAtIndex:0] isKindOfClass:[NSArray class]])) { \
    __args = [__args objectAtIndex:0]; \
  } \
__args; })

Using the above, I can call the following with either an NSArray or with varargs.

// '...' must be objc objects with nil sentinel OR an NSArray with nil sentinel
- (void)someMethod:(NSString *)sql, ...
{
   NSArray *args = VarArgs2(sql);

   // Do stuff with args
}

One more tip is to use the following in the prototype to have the compiler check for the nil sentinel to avoid potential bad things. I got this out of the apple headers...

- (void)someMethod:(NSString *)sql, ... NS_REQUIRES_NIL_TERMINATION;
robottobor
The NS_REQUIRES_NIL_TERMINATION is a good tip. Thanks.
Barry Wark
A: 

There is a nice example how you can go from NSArray to va_list here (see "va_list in Cocoa" and "Creating a fake va_list" sections towards the bottom):

http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html

Here is a teaser ("arguments" is NSArray):

char *argList = (char *)malloc(sizeof(NSString *) * [arguments count]);
[arguments getObjects:(id *)argList];
contents = [[NSString alloc] initWithFormat:formatString arguments:argList];
free(argList);

Not quite python or ruby, but hey...

Vais Salikhov
A: 

You should use new FMDB version http://github.com/ccgus/fmdb. It has the method you need:

- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
Dmitry