可以在Obj-c中为变量参数函数发送一个数组吗?

时间:2009-01-10 22:51:19

标签: objective-c

在python中,很容易构建一个字典或数组,并将其解压缩到一个带有可变参数的函数

我有这个:

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

手动方式是:

[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]];

但我无法对我正在调用的参数进行硬编码,我想:

NSMutableArray *values = [NSMutableArray array];

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

8 个答案:

答案 0 :(得分:18)

Chuck是对的,在Objective-C中没有正确的参数解包。但是,对于需要nil终止的方法(NS_REQUIRES_NIL_TERMINATION),可以使用在index >= count时返回nil的数组访问器扩展变量列表,使其大于所需的变量列表。这肯定是一个黑客,但它确实有效。

// 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

现在,您可以在任何期望使用nil终止的变量参数列表的地方使用NSArrayToVariableArgumentsList(只要您的数组小于10个元素)。例如:

NSArray *otherButtonTitles = @[@"button1", @"button2", @"button3"];
UIActionSheet *actionSheet = [[self alloc] initWithTitle:@"Title"
                                                delegate:self
                                       cancelButtonTitle:@"Cancel"
                                  destructiveButtonTitle:nil
                                       otherButtonTitles:NSArrayToVariableArgumentsList(otherButtonTitles)];

答案 1 :(得分:15)

不幸的是,没有。 Objective-C没有像大量现代语言那样解压缩的参数。我找不到一个解决它的好方法。

问题的一部分是Objective-C本质上只是C.它使用C varargs进行多个参数传递,并且使用varargs没有简单的方法。 A relevant SO discussion

答案 2 :(得分:4)

我想做同样的事情。我想出了以下内容,在输入变量受到一些约束的情况下,它可以正常工作。

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; })

使用上述内容,我可以使用NSArray或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
}

另一个提示是在原型中使用以下内容让编译器检查nil sentinel以避免潜在的坏事。我从苹果标题中得到了这个......

- (void)someMethod:(NSString *)sql, ... NS_REQUIRES_NIL_TERMINATION;

答案 3 :(得分:3)

有一个很好的例子,你可以在这里从NSArray转到va_list(参见“Cocoa中的va_list”和“创建假的va_list”部分):

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

这是一个预告片(“参数”是NSArray):

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

不太蟒蛇或红宝石,但嘿......

答案 4 :(得分:2)

您应该使用新的FMDB版本http://github.com/ccgus/fmdb。它有你需要的方法:

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

答案 5 :(得分:1)

要完成您想要的任务,您必须使用“varargs”,如您的方法所使用的,或者您可以传递一组值,例如[db executeUpdate:sql withValues:vals];,然后提取方法中的值。但是没有办法做更多的“Pythonic”,例如自动解包价值元组,áladef executeUpdate(sql, *args)

答案 6 :(得分:1)

不幸的是(目标 - )C没有提供这样做的方法。在这种情况下,executeUpdate方法需要接受NSArray而不是变量参数列表。

但是,如果您确实知道数组中的条目数量(无论如何您在示例中都有字符串中的数量),您当然可以执行类似

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

如果executeUpdate是外部库方法,并且该库不提供接受NSArray的方法版本,则可以提供自己的包装函数。该函数将查询字符串和数组作为参数。然后,此函数将根据数组的长度使用正确数量的参数调用executeUpdate方法,类似于

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

然后你可以将这个新功能称为

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

这个解决方案的明显缺点是你需要在函数中单独处理所有可能的数组长度,它有很多复制粘贴代码。

答案 7 :(得分:0)

除了robottobor的解决方案: 如果添加以下宏:

#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;\
  }\
}
然后,你可以做一些棘手的事情:

- (id)initWithObjectsAndKeys:(id)firstObject, ...{
    NSArray *objKeyArray = VarArgs2(firstObject);
    splitAlternatingArray(objKeyArray,objs,keys);
    return [self initWithObjects:objs forKeys:keys];
}