Цель C: красивая печать NSArray с различными объектами в каждой позиции индекса
У меня есть NSArray различных объектов, например, NSString и NSNumber целое число.
У меня есть определенная строка формата, которую я хочу использовать для печати содержимого объекта, а не просто с помощью
@"%@|%@"
но, например
@"%-13s|%010d"
Я реализовал следующее для печати типа @"%@|%@", которую нашел здесь в Stackru и в других местах:
@implementation NSString (NSArrayFormatExtension)
+ (id) stringWithFormatFromNSArray: (NSString *) format array: (NSArray*) args;
{
id *argList = malloc( sizeof( id ) * [args count] );
[ args getObjects: argList ];
NSString* result = [ [ [ NSString alloc ] initWithFormat: format arguments: (va_list) argList ] autorelease ];
free(argList);
return result;
}
@end
Вызывается и затем печатается так:
NSString *printstring = [ NSString stringWithFormatFromNSArray: nsFormatString array: nsarray_of_objects ]
что позволит nsFormatStrings типа @"%@ %@" печатать содержимое объекта, но не форматы типа @"%s %d", чтобы печатать базовые типы C с более подробным форматированием.
У меня есть две проблемы и некоторые мысли о решениях, но я не хотел изобретать велосипед здесь.
1.) Я мог бы перебирать элементы NSArray с переключением на тип, производный от className - конечно, - но я задавался вопросом, существует ли стандартный способ сделать это.
Это также означало бы деконструкцию строки формата на отдельные% сущности (или передачу ее в виде массива и работу через нее - не очень чистую, а также необходимость сохранения любых других неформатирующих записей, таких как разделитель канала выше).
2.) Приведенный выше код, использующий @"%@|%@", работает как есть на 32-битном Linux с gcc / objc / objC++ 4.6.X и выше, но на 64-битном Linux segfault, и мне нужно, чтобы он работал на и то и другое.
Из того, что я прочитал, это, кажется, довольно общая проблема с 64-битной реализацией va_list в некоторых системах Linux.
Даже если бы я был доволен форматированием объекта, в отличие от конкретного форматирования типа C, это все равно не работает. У кого-нибудь есть решение для этого? Может показаться, что реализация решения 1) должна быть переносимой во всех случаях, но это
а.) трудоемкий для кодирования и б.) неэффективный.
Мне действительно нужно иметь возможность выполнять подробное форматирование, а также результаты его печати из программы командной строки, которые должны быть прочитаны совершенно другой системой в виде файла CSV или из конвейера или тому подобного.
Возможно, я также захочу использовать разные разделители между полями - например,
....|....;....,....#....^....
который я бы встраивал в мою строку формата просто отлично, если бы мог сделать это таким образом.
Таким образом, любое форматирование с одним "объединяющим" разделителем также не является ответом.
Так намного проще делать такие вещи в Perl;-) - но - этот конкретный кусок программного обеспечения пишется на ObjC++ (связывается с C++ lib).
Ваши мысли и предложения приветствуются.
1 ответ
Причина, по которой ваша программа не работает на некоторых платформах, заключается в том, что она использует непереносимое поведение - приведение массива в стиле C id
с va_list
не гарантируется работа:
(va_list) argList
Это также причина, почему форматирование примитивов, а также %010d
линии, не работает: NSArray
не может содержать примитивы, поэтому int
представлен NSNumber
с int
внутри.
В общем случае C (и, соответственно, Objective C) не поддерживает динамическое создание списков переменных-аргументов. Вот почему вы не можете использовать методы форматирования, которые требуют переменных списков аргументов.
К сожалению, ваш лучший вариант требует много кодирования. Сделайте такой метод:
+ (id) stringWithFormatsFromNSArray: (NSArray*) formats array: (NSArray*) args {
// verify that formats and args have the same number of entries
NSMutableString *res = [NSMutableString string];
for (int i = 0 ; i != args.count ; i++) {
id arg = [args objectAtIndex:i];
id fmt = [formats objectAtIndex:i];
if ([arg isKindOfClass:[NSNumber class]]) {
// Apply fmt to NSNumberFormatter
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setFormat:fmt];
[res appendString:[nf stringFromNumber:arg]];
} else {
[res appendFormat:fmt, arg];
}
}
return [res copy];
}
Этот код предполагает, что первым аргументом является массив, а не строка. Каждый элемент должен содержать формат для соответствующего аргумента. Форматы для номеров будут переданы NSNumberFormatter
, который позволяет форматировать непримитивные объекты, которые представляют числа. Все остальные форматы будут переданы appendFormat
, Обратите внимание, что разделители должны быть включены в строку формата соответствующего объекта. Вы должны добавить разделители перед всеми объектами, кроме начального, или после всех объектов, кроме конечного.