Возможно ли отправить массив в 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];
Ответы
Ответ 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];
}