Подтвердить что ты не робот

Возможно ли отправить массив в Obj-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];
4b9b3361

Ответ 1

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

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

Ответ 2

Чак прав, нет правильного аргумента, распаковывающего в 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 везде, где вы ожидаете список аргументов переменной nil-terminated (до тех пор, пока ваш массив меньше 10 элементов). Например:

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

Ответ 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, либо с помощью 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-часового, чтобы избежать потенциальных плохих вещей. Я получил это из заголовков яблок...

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

Ответ 4

Есть хороший пример того, как вы можете перейти от 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);

Не совсем питон или рубин, но эй...

Ответ 5

Вам следует использовать новую версию FMDB http://github.com/ccgus/fmdb. У этого метода есть необходимый способ:

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

Ответ 6

Чтобы выполнить то, что вы хотите, вы должны использовать "varargs", как использует ваш метод, или вы можете передать массив значений, что-то вроде [db executeUpdate:sql withValues:vals];, а затем вытащите значения в методе. Но нет никакого способа сделать что-то более "Pythonic", например, автоматически распаковывать кортеж значений, la 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];
}