NSString stringWithFormat изменен, чтобы разрешить отсутствующие нумерованные аргументы формата

Основываясь на этот вопрос SO, заданный несколько часов назад, я решили реализовать swizzled метод, который позволит мне использовать отформатированный NSString в качестве аргумента формата в stringWithFormat и не нарушить его при пропуске одной из нумерованных ссылок на аргумент (%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. если не найдено, вызовите обычный stringWithFormat и верните
  3. иначе, переберите аргументы
  4. если формат имеет переменную позиции (%i$@) для позиции i, добавьте аргумент в новый массив аргументов
  5. в противном случае не добавляйте аргумент
  6. возьмите новый массив аргументов, преобразуйте его обратно в va_list и вызовите initWithFormat:arguments:, чтобы получить правильную строку.

Идея состоит в том, что вместо этого я буду запускать все вызовы [NSString stringWithFormat:] через этот метод.

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

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


person coneybeare    schedule 01.06.2010    source источник
comment
Почему вы используете результаты arrayWithCapacity: и stringWithFormat:?   -  person dreamlax    schedule 01.06.2010
comment
я пытался избавиться от некоторых предупреждений компиляции, их можно удалить   -  person coneybeare    schedule 01.06.2010
comment
Вы уверены, что ваш макрос NUMARGS делает то, что вы думаете?   -  person dreamlax    schedule 01.06.2010
comment
это работает для меня ... я так думаю. он просто берет размер массива указателей args, затем делит на размер указателя, чтобы получить количество args   -  person coneybeare    schedule 01.06.2010
comment
@coneybeare: я совершенно уверен, что если бы вы записывали в NSLog результат каждого NUMARGS(args), вы каждый раз получали бы 1. __VA_ARGS__ расширяется до аргументов, переданных самому макросу, а вы предоставляете только один аргумент. Вы вставляете объект типа va_list в массив int; если va_list реализовано как указатель (я уверен, что так и есть в большинстве систем), вы должны получить предупреждение о преобразовании без явного приведения.   -  person dreamlax    schedule 01.06.2010
comment
я проверю ………… Вы правы. но это ничего не меняет. с тем, как работает функция, поскольку это NSMutableArray, который растет по мере того, как я добавляю к нему объекты. Функция работает так, и все ранее разорванные строки больше не разорваны. Основная цель этого вопроса не в том, чтобы заставить функцию работать, а в том, чтобы бросить ее туда для анализа, оптимизации и поделиться этой идеей с другими. Спасибо, что помогли мне увидеть аргументы.   -  person coneybeare    schedule 01.06.2010


Ответы (2)


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

Ваш метод мог бы выглядеть так (если бы это был метод категории для 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. Я не думаю, что вам нужно экранировать $ и @ в формате строки в цикле аргументов.
  4. Я не уверен, что это сработало бы, если бы uaStringWithFormat: было передано нечто большее, чем указатель (т.е. long long, если указатели 32-битные). Это может быть проблемой только в том случае, если ваши переводы также требуют вставки нелокализованных чисел long long величины.
person dreamlax    schedule 01.06.2010
comment
Я стараюсь не редактировать тонны кода. Мой большой проект близок к завершению, и делать массовые изменения кода на этом уровне было бы нехорошо. Вот почему я подумал, что swizzle stringWithFormat будет лучше. - person coneybeare; 01.06.2010
comment
Спасибо. По сути, это то, что я сделал для своего решения, используя описанный выше метод. Теперь я не делаю swizzle, а вместо этого явно вызываю этот метод для более чем 50 методов с несколькими позиционными переменными в моем приложении. Таким образом, все обычные вызовы stringWithFormat не затрагиваются, в то время как я экономлю деньги от переводчиков и экономлю время QA, не меняя все порядки форматирования, строки или что-либо еще, кроме вызовов 50+ методов. - person coneybeare; 02.06.2010
comment
@coneybeare: Я думаю, ты сделал правильный выбор. Методы Swizzling уникальны и интересны, но вы действительно должны быть осторожны при использовании этой техники, особенно в продакшене. Это может показаться большим усилием, но теперь дизайн приложения не требует взломов во время выполнения для работы. - person dreamlax; 02.06.2010

Ого там!

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

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

person Kendall Helmstetter Gelner    schedule 01.06.2010
comment
Строки формата поступают из NSLocalizedString, поэтому они не обнаруживаются во время компиляции. хорошо, если предположить, что я не использую его и вызываю этот метод только при необходимости, есть ли с ним какие-либо серьезные проблемы? - person coneybeare; 01.06.2010