Domanda

C'è un modo per creare una nuova NSString da una stringa di formato come @ "xxx =% @, yyy =% @" e un NSArray di oggetti?

Nella classe NSString ci sono molti metodi come:

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

, ma non di loro prende un NSArray come argomento, e non riesco a trovare un modo per creare un va_list da un NSArray ...

È stato utile?

Soluzione

E 'in realtà non è difficile creare un va_list da un NSArray. Vedi di Matt Gallagher eccellente articolo sull'argomento.

Ecco una categoria NSString di fare ciò che si vuole:

@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

Quindi:

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

Purtroppo, per 64 bit, il formato va_list è cambiato, in modo che il codice di cui sopra non funziona più. E probabilmente non dovrebbe essere usato in ogni caso dato che dipende dal formato che è chiaramente soggette a modifiche. Dato non v'è alcun modo davvero robusto per creare un va_list, una soluzione migliore è quella di limitare semplicemente il numero di argomenti per un massimo ragionevole (diciamo 10) e quindi chiamare stringWithFormat con i primi 10 argomenti, qualcosa di simile a questo:

+ (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] ];
}

Altri suggerimenti

In base a questa risposta utilizzando automatico di conteggio di riferimento (ARC): https://stackoverflow.com/a/8217755/881197

Aggiungi una categoria per NSString con il seguente metodo:

+ (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;
}

Una soluzione che mi è venuta in mente è che ho potuto creare un metodo che funziona con un gran numero fisso di argomenti come:

+ (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];
}

Potrei anche aggiungere un controllo per vedere se la stringa di formato ha più di 21% '' caratteri e un'eccezione in questo caso.

@Chuck è corretto circa il fatto che non si può convertire un NSArray in varargs . Tuttavia, non consiglio la ricerca per il modello %@ nella stringa e la sua sostituzione ogni volta. (Sostituzione caratteri nel bel mezzo di una stringa è generalmente abbastanza inefficiente, e non è una buona idea se si può ottenere lo stesso risultato in un modo diverso.) Ecco un modo più efficiente per creare una stringa con il formato che stai descrivendo:

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];

ho inserito la piscina autorelease per una buona gestione, dal momento che una stringa autoreleased verrà creato per ogni voce di matrice e la matrice mutabile è autoreleased pure. Si potrebbe facilmente rendere questo in un metodo / funzione e ritornare composedString senza trattenere, e gestire l'autorelease altrove nel codice se lo si desidera.

Questa risposta è bacato. Come osservato, non v'è alcuna soluzione a questo problema che è garantito per funzionare quando nuove piattaforme vengono introdotti unicamente con il metodo "10 elemento di matrice".


La risposta da solidsun funzionava bene, fino a quando sono andato a compilare con architettura a 64 bit. Ciò ha causato un errore:

Tipo EXC_BAD_ADDRESS EXC_I386_GPFLT

La soluzione era quella di utilizzare un approccio leggermente diverso per passare la lista degli argomenti al metodo:

+ (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;
}

Questo funziona solo per gli array con un unico elemento

c'è generale modo passare una matrice a una funzione o metodo che utilizza varargs. In questo caso particolare, tuttavia, si potrebbe fingere usando qualcosa come:

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

EDIT: La risposta accettata sostiene che c'è un modo per fare questo, ma indipendentemente da quanto sia fragile questa risposta potrebbe sembrare, questo approccio è molto più fragile. Essa si basa sul comportamento definito dall'implementazione (specificamente, la struttura di un va_list) che non è garantito per rimanere lo stesso. Io sostengo che la mia risposta è corretta e la mia soluzione proposta è meno fragile dato che si basa solo su caratteristiche definite del linguaggio e dei quadri.

Per coloro che hanno bisogno di una soluzione rapida, qui è un'estensione per fare questo in 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
    }

}

Sì, è possibile. Nel GCC mira Mac OS X, almeno, va_list è semplicemente un array di C, così farete una delle id s, poi dire al NSArray per riempirlo:

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);

Si consiglia di non fare affidamento su questa natura nel codice che dovrebbe essere portatile. gli sviluppatori di iPhone, questa è una cosa che dovete assolutamente provare sul dispositivo.

- (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;
}

Testato con il formato e gli argomenti:

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

Risultato: xxx = XXX, yyy = YYY ultimo componente

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

Risultato: xxx = XXX, yyy = YYY ultimo componente

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

Risultato: xxx = XXX ultimo componente

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

Risultato: ultimo componente

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

Risultato: un testo

Ho trovato un certo codice sul web che sostiene che questo è possibile però io non sono riuscito a fare io stesso, se non si conosce il numero di argomenti di anticipo è anche necessario per costruire la stringa di formato in modo dinamico in modo io non vedo il punto.

È meglio solo la costruzione della stringa iterazione l'array.

Si potrebbe trovare lo stringByAppendingString: o stringByAppendingFormat:. Metodo di istanza a portata di mano

Si può creare una categoria per NSString e fare una funzione che riceve un formato, un array e restituisce la stringa con gli oggetti sostituiti.

@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

A causa NSArray è creato in fase di esecuzione, non siamo in grado di fornire allarmi in fase di compilazione, ma possiamo usare NSAssert di dirci se il numero di committenti è pari con il numero di oggetti all'interno dell'array.

su Github dove questa categoria può essere trovato. Anche aggiunto la versione di Chuck utilizzando 'stringByReplacingCharactersInRange:' oltre ad alcuni test.

Utilizzando un milione di oggetti in serie, versione con 'stringByReplacingCharactersInRange:' non scala molto bene (aspettato circa 2 minuti poi chiuse l'applicazione). Utilizzando la versione con NSMutableString, la funzione ha fatto la stringa in circa 4 secondi. Le prove sono state effettuate utilizzando simulatore. Prima dell'uso, i test dovrebbe essere fatto su un dispositivo reale (utilizzare un dispositivo con specifiche più bassi).

Edit: su iPhone 5s versione con NSMutableString prende 10.471655s (un milione di oggetti); su iPhone 5 prende 21.304876s.

Ecco la risposta senza creare esplicitamente un array:

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

Per prima stringa è la stringa di destinazione da formattare, la stringa seguente è la stringa da inserire nel target.

No, non sarà in grado di farlo. le chiamate di argomenti variabili sono risolti in fase di compilazione, e la vostra NSArray ha contenuto solo in fase di esecuzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top