Цель 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, Обратите внимание, что разделители должны быть включены в строку формата соответствующего объекта. Вы должны добавить разделители перед всеми объектами, кроме начального, или после всех объектов, кроме конечного.

Другие вопросы по тегам