Czy istnieje sposób określenia pozycji argumentu / indeksu w NSString stringWithFormat?


83

C # ma składnię, która umożliwia określenie indeksu argumentu w specyfikatorze formatu ciągu, np .:

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

Możesz użyć argumentów więcej niż raz, a także pominąć argumenty, które są używane. Kolejne pytanie dotyczy tego samego formatowania dla C / C ++ w postaci %[index]$[format]np %1$i. Nie udało mi się sprawić, by NSString w pełni przestrzegał tej składni, ponieważ zachowuje się dobrze, gdy pomija argumenty z formatu. Poniższe nie działa zgodnie z oczekiwaniami (EXC_BAD_ACCESS, ponieważ próbuje wyodrębnić ageparametr jako obiekt NSObject *):

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

Argumenty pozycyjne są przestrzegane tylko wtedy, gdy nie ma brakujących argumentów z formatu (co wydaje się dziwnym wymaganiem):

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

Wszystkie te wywołania działają poprawnie w systemie OS X:

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

Czy istnieje sposób na osiągnięcie tego za pomocą NSString w Objective-C / Cocoa? Byłoby to przydatne do celów lokalizacyjnych.


Zgłoś błąd (i daj nam znać numer błędu).
Dirk Theisen,

Odpowiedzi:


126

NSString i CFString obsługują argumenty z możliwością zmiany kolejności / pozycjonowania.

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

Zobacz także dokumentację w Apple: String Resources


5
Zaktualizowałem pytanie o kilka wyjaśnień. Wygląda na to, że Cocoa nie respektuje pominiętych argumentów z formatu, co było efektem ubocznym naruszenia zasad dostępu, które otrzymałem.
Jason

2
Poszanowanie pominiętych argumentów nie jest możliwe ze względu na sposób działania varargs w C. Nie ma standardowego sposobu poznania liczby argumentów lub ich rozmiaru. Analiza ciągów obsługuje to, wnioskując informacje ze specyfikatorów formatu, co wymaga, aby specyfikatory faktycznie tam były.
Jens Ayton

1
Rozumiem, jak działa va_args; jednak wydaje się, że działa to zgodnie z oczekiwaniami: printf ("Wiek:% 2 $ i", [nazwa UTF8String], wiek); Próbowałem innych printf z zmienionymi / brakującymi argumentami i wszystkie dają oczekiwany wynik, podczas gdy NSString nie.
Jason,

7
Aby powtórzyć moje ustalenia, to: stringWithFormat:obsługuje argumenty pozycyjne, o ile wszystkie argumenty są określone w ciągu formatu.
Jason,


1

Poniższy kod naprawia błąd określony w tym problemie. Jest to obejście i ponownie numeruje symbole zastępcze, aby wypełnić luki.

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

0

Po przeprowadzeniu dalszych badań wydaje się, że Cocoa przestrzega składni pozycyjnej w printf. Dlatego alternatywnym wzorem byłoby:

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

Jednak byłoby miło, gdyby była implementacja tego w NSString.


1
sprintfnie jest częścią Cocoa, jest częścią standardowej biblioteki C, a implementacją tego jest stringWithFormat:/ initWithFormat:.
Peter Hosey

Wyjaśniając mój poprzedni komentarz: Wersja Cocoa to stringWithFormat:/ initWithFormat:. To osobna implementacja (obecnie CFStringCreateWithFormat) od implementacji sprintfi znajomych.
Peter Hosey

4
Przypuszczam, że nie ma sensu komentować faktu, że inicjalizacja msg z dokładnie 512 bajtami jest mniej więcej tak bezpieczna, jak wykonanie losowego selektora na losowym obiekcie, ale i tak. Dla każdego, kto nie jest świadomy: bufory o stałej wielkości to jedne z najłatwiejszych sposobów na zwolnienie. google: buffer overflow
George Penn.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.