NSString stringWithFormat перевернулся, чтобы разрешить отсутствующие аргументы нумерованного формата - PullRequest
1 голос
/ 01 июня 2010

Исходя из этого ТАКОГО вопроса, заданного несколько часов назад, я решил реализовать метод swizzled, который позволит мне принять форматированный NSString в качестве аргумента формата в stringWithFormat не пропускается при пропуске одной из пронумерованных ссылок arg (%1$@, %2$@)

У меня это работает, но это первая копия, и, учитывая, что этот метод потенциально может вызываться сотни тысяч раз за запуск приложения, мне нужно отослать это от некоторых экспертов, чтобы увидеть, имеет ли этот метод любые красные флаги, значительные потери производительности или оптимизации

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))
@implementation NSString (UAFormatOmissions)
+ (id)uaStringWithFormat:(NSString *)format, ... {  
    if (format != nil) {
        va_list args;
        va_start(args, format);

        // $@ is an ordered variable (%1$@, %2$@...)
        if ([format rangeOfString:@"$@"].location == NSNotFound) {
            //call apples method
            NSString *s = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
            va_end(args);
            return s;
        }

        NSMutableArray *newArgs = [NSMutableArray arrayWithCapacity:NUMARGS(args)];
        id arg = nil;
        int i = 1;
        while (arg = va_arg(args, id)) {
            NSString *f = [NSString stringWithFormat:@"%%%d\$\@", i];
            i++;
            if ([format rangeOfString:f].location == NSNotFound) continue;
            else [newArgs addObject:arg];
        }
        va_end(args);

        char *newArgList = (char *)malloc(sizeof(id) * [newArgs count]);
        [newArgs getObjects:(id *)newArgList];
        NSString* result = [[[NSString alloc] initWithFormat:format arguments:newArgList] autorelease];
        free(newArgList);
        return result;
    }
    return nil;
}

Основной алгоритм:

  1. поиск строки формата для переменных %1$@, %2$@ путем поиска %@
  2. если не найден, вызовите обычную строкуWithFormat и верните
  3. еще, перебрать арг
  4. если формат имеет переменную позиции (%i$@) для позиции i, добавьте arg в новый массив arg
  5. иначе, не добавляйте аргумент
  6. взять новый массив arg, преобразовать его обратно в va_list и вызвать initWithFormat:arguments:, чтобы получить правильную строку.

Идея состоит в том, что я вместо этого выполняю все вызовы [NSString stringWithFormat:] через этот метод.

Многим это может показаться ненужным, но нажмите на указанный вопрос SO (первая строка), чтобы увидеть примеры того, почему мне нужно это сделать.

Идеи? Мысли? Лучшие реализации? Лучшие решения?

Ответы [ 2 ]

3 голосов
/ 01 июня 2010

Ух ты!

Вместо использования основного метода, в который вы, скорее всего, будете вносить незначительные ошибки, просто включите «Статический анализатор» в настройках вашего проекта, и он будет запускать каждую сборку - если вы укажете неверные аргументы, он выдаст предупреждение компилятора для вас.

Я ценю ваше желание сделать приложение более надежным, но я думаю, что, скорее всего, переписывание этого метода скорее сломает ваше приложение, чем сохранит его.

1 голос
/ 01 июня 2010

Как насчет определения вашего собственного временного метода вместо использования спецификаторов формата и stringWithFormat:? Например, вы можете определить свой собственный метод replaceIndexPoints:, чтобы искать ($1) вместо %1$@. Затем вы отформатируете свою строку и вставите переведенные замены независимо. Этот метод также может принимать массив строк с NSNull или пустыми строками по индексам, которых нет в & ldquo; непереведенном & rdquo; строка.

Ваш метод может выглядеть следующим образом (если бы это был метод категории для NSMutableString):

- (void) replaceIndexPointsWithStrings:(NSArray *) replacements
{
    // 1. look for largest index in "self".
    // 2. loop from the beginning to the largest index, replacing each
    //    index with corresponding string from replacements array.
}

Вот несколько проблем, которые я вижу с вашей текущей реализацией (с первого взгляда):

  1. В комментариях объясняется __VA_ARGS__.
  2. Когда вы используете while (arg = va_arg(args, id)), вы предполагаете, что аргументы nil завершены (например, для arrayWithObjects:), но с stringWithFormat: это не является обязательным требованием.
  3. Я не думаю, что вам нужно экранировать $ и @ в вашем строковом формате в цикле arg.
  4. Я не уверен, что это будет работать хорошо, если uaStringWithFormat: было передано нечто большее, чем указатель (т.е. long long, если указатели 32-битные). Это может быть проблемой, только если ваши переводы также требуют вставки нелокализованных чисел long long.
...