Gibt es eine Möglichkeit, die Position / den Index des Arguments in NSString stringWithFormat anzugeben?

82

C # verfügt über eine Syntax, mit der Sie den Argumentindex in einem Zeichenfolgenformatbezeichner angeben können, z.

string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age);

Sie können Argumente mehrmals verwenden und Argumente, die angegeben wurden, nicht verwenden. Eine andere Frage erwähnt die gleiche Formatierung für C / C ++ in Form von %[index]$[format]z %1$i. Ich konnte NSString nicht dazu bringen, diese Syntax vollständig zu respektieren, da sie sich gut verhält, wenn Argumente aus dem Format weggelassen werden. Folgendes funktioniert nicht wie erwartet (EXC_BAD_ACCESS, da versucht wird, den ageParameter als NSObject * zu dereferenzieren ):

int age = 23;
NSString * name = @"Joe";
NSString * message = [NSString stringWithFormat:@"Age: %2$i", name, age];

Die Positionsargumente werden nur berücksichtigt, wenn im Format keine Argumente fehlen (was eine seltsame Anforderung zu sein scheint):

NSString * message = [NSString stringWithFormat:@"Age: %2$i; Name: %1$@", name, age];

Alle diese Aufrufe funktionieren in OS X ordnungsgemäß:

printf("Age: %2$i", [name UTF8String], age);
printf("Age: %2$i %1$s", [name UTF8String], age);

Gibt es eine Möglichkeit, dies mit NSString in Objective-C / Cocoa zu erreichen? Es wäre nützlich für Lokalisierungszwecke.

Jason
quelle
Reichen Sie einen Fehlerbericht ein (und teilen Sie uns die Fehlernummer mit).
Dirk Theisen

Antworten:

125

NSString und CFString unterstützen nachbestellbare / positionelle Argumente.

NSString *string = [NSString stringWithFormat: @"Second arg: %2$@, First arg %1$@", @"<1111>", @"<22222>"];
NSLog(@"String = %@", string);

Weitere Informationen finden Sie in der Dokumentation unter Apple: String Resources

Jim Correia
quelle
5
Ich habe die Frage mit einigen Erläuterungen aktualisiert. Es scheint, dass Cocoa ausgelassene Argumente aus dem Format nicht berücksichtigt, was ein Nebeneffekt der Zugriffsverletzung war, die ich erhalten habe.
Jason
2
Das Respektieren ausgelassener Argumente ist aufgrund der Funktionsweise von varargs in C nicht möglich. Es gibt keine Standardmethode, um die Anzahl der Argumente oder ihre Größe zu ermitteln. Die String-Analyse behandelt dies, indem die Informationen aus den Formatspezifizierern abgeleitet werden. Dies setzt voraus, dass die Spezifizierer tatsächlich vorhanden sind.
Jens Ayton
1
Ich verstehe, wie va_args funktioniert; Dies scheint jedoch wie erwartet zu funktionieren: printf ("Alter:% 2 $ i", [Name UTF8String], Alter); Ich habe andere printf's mit neu geordneten / fehlenden Argumenten ausprobiert und alle geben die erwartete Ausgabe, während NSString dies nicht tut.
Jason
7
Um meine Ergebnisse noch einmal zu wiederholen: stringWithFormat:Unterstützt Positionsargumente, solange alle Argumente in der Formatzeichenfolge angegeben sind.
Jason
1
Siehe
1

Der folgende Code behebt den in diesem Problem angegebenen Fehler. Dies ist eine Problemumgehung und nummeriert die Platzhalter neu, um Lücken zu schließen.

+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments 
{
    NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count];
    NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format];
    NSString *placeHolderFormat = @"%%%d$";

    int actualPlaceholderIndex = 1;

    for (int i = 1; i <= arguments.count; ++i) {
        NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i];
        if ([format rangeOfString:placeHolder].location != NSNotFound) {
            [filteredArguments addObject:[arguments objectAtIndex:i - 1]];

            if (actualPlaceholderIndex != i) {
                NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex];
                [correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];    
                [replacementPlaceHolder release];
            }
            actualPlaceholderIndex++;
        }
        [placeHolder release];
    }

    if (filteredArguments.count == 0) {
        //No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported.
        [filteredArguments setArray:arguments];
    }

    NSString* result;
    if (filteredArguments.count == 0) {
        //Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string
        result = [NSString stringWithString:format];
    } else {
        char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]);
        [filteredArguments getObjects:(id *)argList];
        result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease];
        free(argList);    
    }

    [filteredArguments release];
    [correctedFormat release];

    return result;
}
Werner Altewischer
quelle
0

Nach weiteren Recherchen scheint Cocoa die Positionssyntax in zu respektieren printf. Daher wäre ein alternatives Muster:

char msg[512] = {0};
NSString * format = @"Age %2$i, Name: %1$s"; // loaded from resource in practice
sprintf(msg, [format UTF8String], [name UTF8String], age);
NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];

Es wäre jedoch schön, wenn dies auf NSString implementiert würde.

Jason
quelle
1
sprintfist nicht Teil von Cocoa, sondern Teil der C-Standardbibliothek, und die Implementierung davon ist stringWithFormat:/ initWithFormat:.
Peter Hosey
Klarstellung meines vorherigen Kommentars: Die Cocoa-Version ist stringWithFormat:/ initWithFormat:. Es ist eine separate Implementierung (derzeit CFStringCreateWithFormat) von der von sprintfund Freunden.
Peter Hosey
4
Ich nehme an, es hat wenig Sinn, die Tatsache zu kommentieren, dass das Initialisieren von msg mit genau 512 Bytes ungefähr so ​​sicher ist wie das Ausführen einer Zufallsauswahl für ein zufälliges Objekt, aber trotzdem. Für alle, die es nicht wissen: Puffer mit fester Größe sind einige der einfachsten Möglichkeiten, um ausgelöst zu werden. google: Pufferüberlauf
George Penn.