Нет переносимого способа создания va_lists в C, C ++ или Objective-C. Возможно, вы могли бы написать или найти некоторые функции, которые используют встроенную сборку для прямой модификации стека и выполнения различных вызовов, но на самом деле это не очень хороший способ. У меня есть три практических варианта, о которых я могу подумать.
Вот первый вариант. Используйте только изменяемые строки и передавайте аргументы, используя initSithFormat: arguments: метод NSString.
- (NSString*)forwardMessage:(NSString*)format, ... {
va_list args;
va_start(args, format);
BOOL escape = NO;
char* ptr = (char*)[format UTF8String];
while (*ptr) {
if (*ptr == '%') {
escape = !escape;
} else if (escape) {
// argument
id obj = va_arg(args, id);
if (*ptr == '@') {
// object
if ([obj isKindOfClass:[NSString class]]) {
// string
id copy = [obj copy];
if (copy != obj) {
// mutable
[obj replaceCharactersInRange:NSMakeRange(0, [obj length]) withString:@"replaced!"];
}
}
}
escape = NO;
}
++ptr;
}
va_end(args);
va_list args2;
va_start(args2, format);
NSString* ret = [[NSString alloc] initWithFormat:format arguments:args2];
va_end(args2);
return ret;
}
Этот метод будет принимать переменные аргументы и заменяет содержимое любой изменяемой строки словом «replace!». Поскольку мы можем только прочитать аргументы перед их пересылкой, мы не можем на самом деле отправлять разные объекты в initWithFormat: arguments: нам просто нужно изменить объекты. Имейте в виду, что создание копии объекта для проверки, является ли он изменчивым, как я делал в этом методе, не является хорошей практикой.
Вот ваш второй вариант. Используйте NSInvocation для создания новых аргументов для stringWithFormat:.
- (NSString*)forwardMessage:(NSString*)format, ... {
BOOL escape = NO;
NSUInteger count = 0;
char* ptr = (char*)[format UTF8String];
while (*ptr) {
if (*ptr == '%') {
escape = !escape;
} else if (escape) {
if (*ptr == '@') {
// this is an object
}
++count;
escape = NO;
}
++ptr;
}
char* sig = malloc(3 + count + 2);
memset(sig, '@', 3 + count);
sig[3 + count] = ':';
sig[3 + count + 1] = '\0';
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:sig]];
free(sig);
[invocation setTarget:[NSString class]];
[invocation setSelector:@selector(stringWithFormat:)];
[invocation setArgument:&format atIndex:2];
va_list args;
va_start(args, format);
for (NSUInteger i = 0; i < count; ++i) {
void* arg = va_arg(args, void*);
// arg is an object, you can change it here
[invocation setArgument:&arg atIndex:i + 3];
}
[invocation invoke];
va_end(args);
id ret;
[invocation getReturnValue:&ret];
return ret;
}
Этот метод имеет один недостаток: он не будет работать, когда вы передаете типы, размеры которых не совпадают с void * или id. Например, целые числа работают, но числа с плавающей точкой не получаются правильно. [self forwardMessage:@"test %d asd %s %f %@ %d", 2, "asd", 2.897, @"test" 5]
возвращает test 2 asd asd 0.000000 test 5
. Хотя было бы не сложно добавить немного больше логики для правильной работы определенных типов.
Я не уверен, что могу порекомендовать одно из этих решений, но, возможно, они подойдут для вашей ситуации.
Третий вариант: если ваши аргументы будут только объектами NSString, я должен порекомендовать отказаться от stringWithFormat: и проанализировать / сформировать форматированную строку самостоятельно.