Можно ли отправить массив в Obj-c для функции переменных аргументов? - PullRequest
12 голосов
/ 11 января 2009

В 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 ]

18 голосов
/ 06 ноября 2013

Чак прав, в Objective-C нет подходящего аргумента для распаковки. Однако, для методов, которые требуют завершения nil (NS_REQUIRES_NIL_TERMINATION), вы можете расширить список переменных больше, чем нужно, используя метод доступа к массиву, который возвращает nil, когда index >= count. Это, безусловно, взломать, но это работает.

// 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)];
15 голосов
/ 11 января 2009

К сожалению, нет. Objective-C не имеет аргументов для распаковки, как во многих современных языках. Нет даже хорошего способа обойти это, что я когда-либо нашел.

Часть проблемы заключается в том, что Objective-C по сути просто C. Он выполняет множественные аргументы, передавая C-переменные, и не существует простого способа сделать это с помощью Varargs. Соответствующее обсуждение SO .

4 голосов
/ 16 января 2009

Я хотел сделать то же самое. Я придумал следующее, которое отлично работает, учитывая некоторые ограничения на входные переменные.

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 голосов
/ 11 июня 2009

Вот хороший пример того, как вы можете перейти от NSArray к va_list здесь (см. Разделы «va_list in Cocoa» и «Создание поддельного 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, но эй ...

2 голосов
/ 20 июля 2010

Вы должны использовать новую версию FMDB http://github.com/ccgus/fmdb. Он имеет метод, который вам нужен:

- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
1 голос
/ 11 января 2009

К сожалению (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);

Очевидным недостатком этого решения является то, что вам нужно обрабатывать все возможные длины массива отдельно в функции, и в нем много кода для копирования-вставки.

1 голос
/ 11 января 2009

Чтобы выполнить то, что вы хотите, вы должны использовать «varargs», как ваш метод использует, или вы можете передать массив значений, например, [db executeUpdate:sql withValues:vals];, а затем извлечь значения в методе. Но нет никакого способа сделать что-то более «Pythonic», такое как автоматическая распаковка кортежа значений, как у def executeUpdate(sql, *args).

0 голосов
/ 16 марта 2011

дополнительно к решению роботобора: если вы добавите следующий макрос:

#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];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...