Подтвердить что ты не робот

Как создать NSString из строки формата, например @ "xxx =% @, yyy =% @" и NSArray объектов?

Есть ли способ создать новый NSString из строки формата, такой как @ "xxx =% @, yyy =% @" и NSArray объектов?

В классе NSSTring существует много методов:

- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
- (id)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
+ (id)stringWithFormat:(NSString *)format, ...

но не из них принимает NSArray как аргумент, и я не могу найти способ создания va_list из NSArray...

4b9b3361

Ответ 1

На самом деле нетрудно создать va_list из NSArray. См. Matt Gallagher отличная статья по этому вопросу.

Ниже приведена категория NSString:

@interface NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;

@end

@implementation NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    char *argList = (char *)malloc(sizeof(NSString *) * arguments.count);
    [arguments getObjects:(id *)argList];
    NSString* result = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
    free(argList);
    return result;
}

@end

Тогда:

NSString* s = [NSString stringWithFormat:@"xxx=%@, yyy=%@" array:@[@"XXX", @"YYY"]];
NSLog( @"%@", s );

К сожалению, для 64-битного формата формат va_list изменился, поэтому вышеуказанный код больше не работает. И, вероятно, не следует использовать, так как это зависит от формата, который может быть изменен. Учитывая, что нет действительно надежного способа создания va_list, лучшим решением является простое ограничение количества аргументов до разумного максимума (скажем, 10), а затем вызов stringWithFormat с первыми 10 аргументами, примерно так:

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    if ( arguments.count > 10 ) {
        @throw [NSException exceptionWithName:NSRangeException reason:@"Maximum of 10 arguments allowed" userInfo:@{@"collection": arguments}];
    }
    NSArray* a = [arguments arrayByAddingObjectsFromArray:@[@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X"]];
    return [NSString stringWithFormat:format, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ];
}

Ответ 2

На основе этого ответа используется автоматический подсчет ссылок (ARC): fooobar.com/questions/177826/...

Добавьте категорию в NSString следующим способом:

+ (id)stringWithFormat:(NSString *)format array:(NSArray *)arguments
{
    NSRange range = NSMakeRange(0, [arguments count]);
    NSMutableData *data = [NSMutableData dataWithLength:sizeof(id) * [arguments count]];
    [arguments getObjects:(__unsafe_unretained id *)data.mutableBytes range:range];
    NSString *result = [[NSString alloc] initWithFormat:format arguments:data.mutableBytes];
    return result;
}

Ответ 3

Одно из решений, которые пришли мне на ум, заключается в том, что я могу создать метод, который работает с фиксированным большим количеством аргументов, например:

+ (NSString *) stringWithFormat: (NSString *) format arguments: (NSArray *) arguments {
    return [NSString stringWithFormat: format ,
          (arguments.count>0) ? [arguments objectAtIndex: 0]: nil,
          (arguments.count>1) ? [arguments objectAtIndex: 1]: nil,
          (arguments.count>2) ? [arguments objectAtIndex: 2]: nil,
          ...
          (arguments.count>20) ? [arguments objectAtIndex: 20]: nil];
}

Я мог бы также добавить проверку, чтобы увидеть, имеет ли строка формата более 21 символа '%' и генерирует исключение в этом случае.

Ответ 4

@Чак правильно говорит о том, что вы не можете преобразовать NSArray в varargs. Однако я не рекомендую искать шаблон %@ в строке и каждый раз заменять его. (Замена символов в середине строки, как правило, довольно неэффективна, и не очень хорошая идея, если вы можете сделать то же самое по-другому.) Вот более эффективный способ создания строки с форматом, который вы описываете:

NSArray *array = ...
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    [newArray addObject:[NSString stringWithFormat:@"x=%@", [object description]]];
}
NSString *composedString = [[newArray componentsJoinedByString:@", "] retain];
[pool drain];

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

Ответ 5

Этот ответ глючит. Как уже отмечалось, решения этой проблемы не существует, что гарантировано работать, когда новые платформы вводятся иначе, чем использование метода "10 элементов массива".


Ответ solidsun работал хорошо, пока я не пошел на компиляцию с 64-битной архитектурой. Это вызвало ошибку:

EXC_BAD_ADDRESS тип EXC_I386_GPFLT

Решение заключалось в использовании немного другого подхода для передачи списка аргументов методу:

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
     __unsafe_unretained id  * argList = (__unsafe_unretained id  *) calloc(1UL, sizeof(id) * arguments.count);
    for (NSInteger i = 0; i < arguments.count; i++) {
        argList[i] = arguments[i];
    }

    NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;//  arguments:(void *) argList];
    free (argList);
    return result;
}

Это работает только для массивов с одним элементом

Ответ 6

Там не является общим способом, чтобы передать массив функции или метода, который использует varargs. В этом конкретном случае, однако, вы можете подделать его, используя что-то вроде:

for (NSString *currentReplacement in array)
    [string stringByReplacingCharactersInRange:[string rangeOfString:@"%@"] 
            withString:currentReplacement];

EDIT: принятый ответ утверждает, что есть способ сделать это, но независимо от того, насколько хрупким этот ответ может показаться, этот подход гораздо более хрупкий. Он полагается на поведение, определяемое реализацией (в частности, на структуру va_list), которая не гарантирует, что она останется прежней. Я утверждаю, что мой ответ верен, и мое предлагаемое решение менее хрупко, поскольку оно зависит только от определенных особенностей языка и фреймворков.

Ответ 7

Для тех, кто нуждается в решении Swift, вот расширение для этого в Swift

extension String {

    static func stringWithFormat(format: String, argumentsArray: Array<AnyObject>) -> String {
        let arguments = argumentsArray.map { $0 as! CVarArgType }
        let result = String(format:format, arguments:arguments)
        return result
    }

}

Ответ 8

Да, это возможно. В GCC, ориентированном на Mac OS X, по крайней мере, va_list является просто массивом C, поэтому вы сделаете один из id s, затем скажите NSArray заполнить его:

NSArray *argsArray = [[NSProcessInfo processInfo] arguments];
va_list args = malloc(sizeof(id) * [argsArray count]);
NSAssert1(args != nil, @"Couldn't allocate array for %u arguments", [argsArray count]);

[argsArray getObjects:(id *)args];

//Example: NSLogv is the version of NSLog that takes a va_list instead of separate arguments.
NSString *formatSpecifier = @"\n%@";
NSString *format = [@"Arguments:" stringByAppendingString:[formatSpecifier stringByPaddingToLength:[argsArray count] * 3U withString:formatSpecifier startingAtIndex:0U]];
NSLogv(format, args);

free(args);

Вы не должны полагаться на эту природу в коде, который должен быть переносимым. Разработчики iPhone, это одно, что вам обязательно нужно проверить на устройстве.

Ответ 9

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

Лучше просто построить строку, итерации массива.

Вы можете найти метод stringByAppendingString: или stringByAppendingFormat: instance удобным.

Ответ 10

Можно создать категорию для NSString и создать функцию, которая получает формат, массив и возвращает строку с замененными объектами.

@interface NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments;

@end

@implementation NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments {
    static NSString *objectSpecifier = @"%@"; // static is redundant because compiler will optimize this string to have same address
    NSMutableString *string = [[NSMutableString alloc] init]; // here we'll create the string
    NSRange searchRange = NSMakeRange(0, [format length]);
    NSRange rangeOfPlaceholder = NSMakeRange(NSNotFound, 0); // variables are declared here because they're needed for NSAsserts
    NSUInteger index;
    for (index = 0; index < [arrayArguments count]; ++index) {
        rangeOfPlaceholder = [format rangeOfString:objectSpecifier options:0 range:searchRange]; // find next object specifier
        if (rangeOfPlaceholder.location != NSNotFound) { // if we found one
            NSRange substringRange = NSMakeRange(searchRange.location, rangeOfPlaceholder.location - searchRange.location);
            NSString *formatSubstring = [format substringWithRange:substringRange];
            [string appendString:formatSubstring]; // copy the format from previous specifier up to this one
            NSObject *object = [arrayArguments objectAtIndex:index];
            NSString *objectDescription = [object description]; // convert object into string
            [string appendString:objectDescription];
            searchRange.location = rangeOfPlaceholder.location + [objectSpecifier length]; // update the search range in order to minimize search
            searchRange.length = [format length] - searchRange.location;
        } else {
            break;
        }
    }
    if (rangeOfPlaceholder.location != NSNotFound) { // we need to check if format still specifiers
        rangeOfPlaceholder = [format rangeOfString:@"%@" options:0 range:searchRange];
    }
    NSAssert(rangeOfPlaceholder.location == NSNotFound, @"arrayArguments doesn't have enough objects to fill specified format");
    NSAssert(index == [arrayArguments count], @"Objects starting with index %lu from arrayArguments have been ignored because there aren't enough object specifiers!", index);
    return string;
}

@end

Поскольку NSArray создается во время выполнения, мы не можем предоставлять предупреждения во время компиляции, но мы можем использовать NSAssert, чтобы сообщить нам, если количество спецификаторов равно числу объектов внутри массива.

Создал проект в Github, где эта категория может быть найдена. Также добавлена ​​версия Chuck с помощью 'stringByReplacingCharactersInRange:' плюс некоторые тесты.

Используя миллион объектов в массиве, версия с 'stringByReplacingCharactersInRange:' не очень хорошо масштабируется (подождите около 2 минут, затем закрывает приложение). Используя версию с NSMutableString, функция произвела строку примерно через 4 секунды. Тесты проводились с использованием тренажера. Перед использованием тесты должны выполняться на реальном устройстве (используйте устройство с наименьшими параметрами).

Изменить: на iPhone 5s версия с NSMutableString занимает 10.471655s (один миллион объектов); на iPhone 5 занимает 21.304876s.

Ответ 11

- (NSString *)stringWithFormat:(NSString *)format andArguments:(NSArray *)arguments {
    NSMutableString *result = [NSMutableString new];
    NSArray *components = format ? [format componentsSeparatedByString:@"%@"] : @[@""];
    NSUInteger argumentsCount = [arguments count];
    NSUInteger componentsCount = [components count] - 1;
    NSUInteger iterationCount = argumentsCount < componentsCount ? argumentsCount : componentsCount;
    for (NSUInteger i = 0; i < iterationCount; i++) {
        [result appendFormat:@"%@%@", components[i], arguments[i]];
    }
    [result appendString:[components lastObject]];
    return iterationCount == 0 ? [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] : result;
}

Протестировано с помощью формата и аргументов:

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

Результат: xxx = XXX, yyy = последний компонент YYY

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY"];

Результат: xxx = XXX, yyy = последний компонент YYY

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX"];

Результат: xxx = XXX последний компонент

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[];

Результат: последний компонент

NSString *format = @"some text";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

Результат: некоторый текст

Ответ 12

Здесь ответ без явного создания массива:

   NSString *formattedString = [NSString stringWithFormat:@"%@ World, Nice %@", @"Hello", @"Day"];

Первая строка - это целевая строка для форматирования, следующая строка - это строка, которая должна быть вставлена ​​в цель.

Ответ 13

Нет, вы не сможете. Переменные вызовы аргументов решаются во время компиляции, а ваш NSArray имеет содержимое только во время выполнения.