【问题标题】:Is possible send a array in Obj-c for a variable arguments function?是否可以在 Obj-c 中为可变参数函数发送一个数组?
【发布时间】:2009-01-10 22:51:19
【问题描述】:

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

【问题讨论】:

    标签: objective-c


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 这样的 hack,但是一个可爱的 hack。
    • array 在哪里使用?
    • @BenC.R.Leggiero 很好,修复了答案中的变量名
    【解决方案2】:

    很遗憾,没有。 Objective-C 不像许多现代语言那样具有参数解包功能。我什至没有找到解决它的好方法。

    部分问题在于,Objective-C 本质上只是 C。它使用 C 可变参数传递多个参数,并且没有简单的方法可以使用可变参数来做到这一点。 A relevant SO discussion.

    【讨论】:

    • 我刚刚从另一篇文章中发现,如果需要,您可以根据需要创建此功能,尽管默认情况下 Cocoa 没有这样的方法。 stackoverflow.com/questions/1058736/#1061750
    • 我已经有一段时间没有对此进行调查了,但我认为创建任意可变参数列表的方法有些脆弱。这取决于无法保证的实现细节。只是一个警告。
    【解决方案3】:

    我想做同样的事情。考虑到输入变量的一些限制,我想出了以下方法,效果很好。

    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 或可变参数调用以下命令。

    // '...' 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 标记以避免潜在的坏事。我从苹果标题中得到了这个......

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

    【讨论】:

      【解决方案4】:

      这里有一个很好的例子,你可以从 NSArray 转到 va_list(参见底部的“va_list in Cocoa”和“Creating a fake 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);
      

      不完全是 python 或 ruby​​,但是嘿...

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          要完成你想要的,你必须使用“varargs”,就像你的方法使用的那样,或者你可以传入一个值数组,比如[db executeUpdate:sql withValues:vals];,然后拉出方法中的值。但是没有办法做一些更“Pythonic”的事情,比如自动解压一组值,就像def executeUpdate(sql, *args)

          【讨论】:

            【解决方案7】:

            不幸的是(Objective-)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);
            

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

            【讨论】:

              【解决方案8】:

              除了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];
              }
              

              【讨论】:

                猜你喜欢
                • 2011-01-04
                • 2023-03-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-08-08
                • 1970-01-01
                相关资源
                最近更新 更多