Как создать строку NSString из строки формата, такой как @ & ldquo; xxx =% @, yyy =% @ & rdquo; а NSArray объектов?

Мне кажется, вам просто нужно вызвать preventDefault для события.

 $(window).bind('beforeunload', function(e){
        // check if any field is dirty 
        if ($('div.form').dirtyForms('isDirty')) {
            var modalParams = {
                Content: 'You have some unsaved changes, proceed?'
                OnSave: navigateAndSave,
                OnCancel: e.preventDefault();
            }
           ShowModal(modalParams);
        }
    })

function navigateAndSave() {
    // Do stuff
};
29
задан Quinn Taylor 29 June 2009 в 17:42
поделиться

13 ответов

На самом деле нетрудно создать va_list из NSArray. См. Превосходную статью Мэтта Галлахера на эту тему.

Вот категория 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 аргументами, что-то вроде этого: ]

46
ответ дан Gregor 29 June 2009 в 17:42
поделиться

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

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

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

-2
ответ дан Tom 29 June 2009 в 17:42
поделиться

Можно создать категорию для 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, где можно найти эту категорию. Также добавлена ​​версия Чака с использованием 'stringByReplacingCharactersInRange:' плюс несколько тестов.

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

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

0
ответ дан Athan 29 June 2009 в 17:42
поделиться

Да, это возможно. В 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, это одна вещь, которую вы обязательно должны протестировать на устройстве.

2
ответ дан Peter Hosey 29 June 2009 в 17:42
поделиться

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

-3
ответ дан Marco Mustapic 29 June 2009 в 17:42
поделиться

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

Вам лучше просто построить строку, перебирая массив.

Вы можете найти удобный для себя метод stringByAppendingString: или stringByAppendingFormat: экземпляр.

0
ответ дан Ron Srebro 29 June 2009 в 17:42
поделиться

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

extension String {

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

}
3
ответ дан Bocaxica 29 June 2009 в 17:42
поделиться

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


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

EXC_BAD_ADDRESS type 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;
}

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

4
ответ дан Brett 29 June 2009 в 17:42
поделиться
- (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"];

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

1
ответ дан Ruslan Soldatenko 29 June 2009 в 17:42
поделиться

На основании этого ответа с использованием автоматического подсчета ссылок (ARC): https://stackoverflow.com/a/8217755/881197

Добавить категорию в 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;
}
35
ответ дан Community 29 June 2009 в 17:42
поделиться

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

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

РЕДАКТИРОВАТЬ: Принятый ответ утверждает, что есть способ сделать это, но независимо от того, насколько хрупким может показаться этот ответ, что подход гораздо более хрупкий. Он опирается на поведение, определяемое реализацией (в частности, структуру va_list), которое не гарантируется тем же. Я утверждаю, что мой ответ правильный, и предложенное мной решение менее хрупкое, поскольку оно опирается только на определенные особенности языка и структур.

3
ответ дан Community 29 June 2009 в 17:42
поделиться

@Chuck прав в том, что вы не можете конвертировать 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, не сохраняя его, и при желании обработать авторелиз в другом месте кода.

4
ответ дан Community 29 June 2009 в 17:42
поделиться

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

+ (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 символа '%', и в этом случае выдается исключение.

16
ответ дан Panagiotis Korros 29 June 2009 в 17:42
поделиться