Come creare un NSString da una stringa di formato come @ “xxx =% @, yyy =% @” e un NSArray di oggetti?
-
21-08-2019 - |
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 ...
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.