Reputation: 16186
In python it is easy to 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 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 I'm 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];
Upvotes: 12
Views: 12073
Reputation: 3524
Chuck is right, there's no proper argument unpacking in Objective-C. However, for methods that require nil termination (NS_REQUIRES_NIL_TERMINATION), you can expand the variable list larger than what is needed by using an array accessor that returns nil when index >= count
. This is most certainly a hack, but it works.
// Return nil when __INDEX__ is beyond the bounds of the array
#define NSArrayObjectMaybeNil(__ARRAY__, __INDEX__) ((__INDEX__ >= [__ARRAY__ count]) ? nil : [__ARRAY__ objectAtIndex:__INDEX__])
// Manually expand an array into an argument list
#define NSArrayToVariableArgumentsList(__ARRAYNAME__)\
NSArrayObjectMaybeNil(__ARRAYNAME__, 0),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 1),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 2),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 3),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 4),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 5),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 6),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 7),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 8),\
NSArrayObjectMaybeNil(__ARRAYNAME__, 9),\
nil
Now you can use NSArrayToVariableArgumentsList
wherever you expect a nil-terminated variable argument list (as long as your array is smaller than 10 elements). For example:
NSArray *otherButtonTitles = @[@"button1", @"button2", @"button3"];
UIActionSheet *actionSheet = [[self alloc] initWithTitle:@"Title"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:NSArrayToVariableArgumentsList(otherButtonTitles)];
Upvotes: 18
Reputation: 4938
additionally to robottobor's solution: if you add the following macro:
#define splitAlternatingArray(args,arg1,arg2) \
NSMutableArray *arg1 = [NSMutableArray array];\
NSMutableArray *arg2 = [NSMutableArray array];\
{\
BOOL isFirst = YES;\
for (id arg in args) {\
if (isFirst) {\
[arg1 addObject:arg];\
} else {\
[arg2 addObject:arg];\
}\
isFirst = !isFirst;\
}\
}
you can then do tricky things like:
- (id)initWithObjectsAndKeys:(id)firstObject, ...{
NSArray *objKeyArray = VarArgs2(firstObject);
splitAlternatingArray(objKeyArray,objs,keys);
return [self initWithObjects:objs forKeys:keys];
}
Upvotes: 0
Reputation: 1041
You should use new FMDB version http://github.com/ccgus/fmdb. It has the method you need:
- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
Upvotes: 2
Reputation:
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...
Upvotes: 3
Reputation: 11762
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;
Upvotes: 4
Reputation: 366
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.
Upvotes: 1
Reputation: 237080
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.
Upvotes: 15
Reputation: 410932
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)
.
Upvotes: 1