2013-08-26 15 views
5

Używam makra uprościć powrocie zlokalizowane ciągi, tak:Objective-C: „format string nie jest ciągiem dosłowny (potencjalnie niebezpieczny)” ostrzeżenie z makro

#define GetLocalStr(key, ...) \ 
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__] 

Zasadniczo, jeśli masz wpis w Strings lokalizacji pliku, podobnie jak "name" = "My name is %@";, nazywając

GetLocalStr(@"name", @"Foo"); 

zwróci NSString @"My name is Foo"

Gdy go jednak, jak:

NSString * str = GetLocalStr(@"name", @"Foo"); 

Otrzymuję komunikat "ciąg formatu nie jest literałem ciąg". Nawet po rady innych odpowiedzi na SO temat tego ostrzeżenia i zastąpienie go:

NSString * str = [NSString stringWithFormat:@"%@", GetLocalStr(@"name", @"Foo")]; 

wciąż otrzymuję ostrzeżenie, a poza tym to rodzaj pokonuje punkt makro ułatwienie życia.

Jak mogę pozbyć się ostrzeżenia przed owijaniem wszystkich połączeń GetLocalStr w tłumikach #pragma?

Edycja 27/08

Po uruchomieniu przez odpowiedź CRD i robi kilka testów, wydaje się, że zrobiłem złe założenie o błędzie. Aby wyjaśnić:

plik lokalizacyjne Struny:

"TestNoArgs" = "Hello world"; 
"TestArgs" = "Hello world %@"; 

Kod:

NSString * str1 = GetLocalStr(@"TestNoArgs"); // gives warning 
NSString * str2 = GetLocalStr(@"TestArgs", @"Foo"); // doesn't give warning 

Większość moich tłumaczeń podjąć żadnych argumentów, a te były te dające ostrzeżenie, ale Nie nawiązałem połączenia, dopóki nie przeczytam odpowiedzi CRD.

zmieniłem jednego makra do dwóch, tak jak poniżej:

#define GetLocalStrNoArgs(key) \ 
    [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil] 

#define GetLocalStrArgs(key, ...) \ 
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__] 

A jeśli zadzwonię każdy oddzielnie, nie ma ostrzeżenia.

Chciałbym GetLocalStr rozwinąć albo GetLocalStrNoArgs lub GetLocalStrArgs zależnie czy jakieś argumenty zostały przekazane lub nie, ale tak daleko I już nie mając szczęścia (makra nie są moją mocną stroną: D).

Używam sizeof(#__VA_ARGS__) do określenia, czy są jakieś argumenty przekazane - to stringifys argumentów, a jeśli rozmiar jest 1, jest pusty (tj. "\ 0"). Być może nie jest to metoda najbardziej idealna, ale wydaje się działać.

Gdybym przepisać mój GetLocalStr makro do:

#define GetLocalStr(key,...) (sizeof(#__VA_ARGS__) == 1) ? GetLocalStrNoArgs(key) : GetLocalStrArgs(key,##__VA_ARGS__) 

mogę go używać, ale ja wciąż dostać ostrzeżenia wszędzie jest używany i nie ma przekazywane argumenty, podczas gdy coś

#define GetLocalStr(key,...)    \ 
    #if (sizeof(#__VA_ARGS__) == 1)  \ 
     GetLocalStrNoArgs(key)    \ 
    #else         \ 
     GetLocalStrArgs(key,##__VA_ARGS__) 

won” t skompilować. Jak mogę uzyskać prawidłowe powiększanie makra GetLocalStr?

+0

'@" name "= @" Nazywam się% @ ";' - literały nie są przypisywane, nie będą się kompilować. 'NSString * format = @" Nazywam się% @ "; NSString * str = GetLocalStr (format, @ "Foo"); NSLog (@ "% @", str); 'nie wyrzuca żadnych ostrzeżeń (spodziewałem się, że tak jest tylko w definicji' GetLocalStr'), możesz chcieć poprawić cytaty i podać więcej szczegółów na temat miejsca ostrzeżenia. –

+1

Możesz utworzyć kategorię NSString, aby zrobić to za Ciebie. –

+0

@ A-Live "@" name "= @" Moja nazwa to% @ "' linia znajduje się w pliku lokalizacyjnym Strings, więc technicznie powinna być po prostu "name" = "Moja nazwa to% @" - przepraszam, jeśli to nie było jasne. Ostrzeżenie jest oznaczone na linii 'NSString * str = GetLocalStr (...);' linia – divillysausages

Odpowiedz

5

Kompilatory GCC sprawdzają, czy łańcuchy formatów i podane argumenty są zgodne, nie mogą tego zrobić, jeśli ciąg formatu nie jest literałem - stąd komunikat o błędzie, który pojawia się podczas pobierania ciągu formatu z pakietu.

Aby rozwiązać ten problem, istnieje atrybut, format_arg(n) (docs), aby zaznaczyć funkcje, które przyjmują ciąg formatu; zmienia go w jakiś sposób bez zmieniając faktyczne specyfikatory formatu, np przetłumaczyć; a następnie zwróć go. Kakao zapewnia wygodne makro NS_FORMAT_ARG(n) dla tego atrybutu.

Aby rozwiązać problem trzeba zrobić dwie rzeczy:

  1. Owiń się wezwanie do NSBundle w funkcji z tego atrybutu określonego; i

  2. Zmień swój „klucz” do włączenia formatami.

drugie pierwszy plik ciągi powinny zawierać:

"name %@" = "My name is %@" 

więc kluczem ma takie same specyfikacje dotyczące formatu jak wynik (jeśli trzeba zmienić kolejność specyfikatorów dla danego języka użyć formatu pozycyjną specifiers).

teraz zdefiniować prostą funkcję do zrobienia odnośnika, przypisując go jako funkcja translacji Format. Uwaga możemy oznaczyć ją jako static inline, używając makra NS_INLINE jako wskazówka do kompilatora zarówno inline go do rozwijania makr; static pozwala na włączenie go w wielu plików bez starć Symbol:

NS_INLINE NSString *localize(NSString *string) NS_FORMAT_ARGUMENT(1); 
NSString *localize(NSString *string) 
{ 
    return [[NSBundle mainBundle] localizedStringForKey:string value:@"" table:nil]; 
} 

I makra staje:

#define GetLocalStr(key, ...) [NSString stringWithFormat:localize(key), ##__VA_ARGS__] 

Teraz, gdy:

GetLocalStr(@"name %@", @"Foo") 

dostaniesz zarówno zlokalizowane sprawdzanie łańcucha i formatu.

Aktualizacja

Po komentarzu Grega Wróciłem i sprawdzone - Miałem powielana swój błąd i tak Zakłada było aż do brakującego atrybutu. Jednak, jak zaznacza localizedStringForKey:value:table: Greg już posiada atrybut, więc dlaczego błąd?Co miałem zrobić w roztargnieniu odtwarzając swój błąd:

NSLog(GetLocalStr(@"name %@", @"Foo")); 

i kompilator wskazał definicji makro a nie linię - Powinienem był zauważony kompilator został mylące mnie.

Więc gdzie to cię opuści? Może zrobiłeś coś podobnego? Klucz jest taki, że ciąg formatu musi być literałem lub wynikiem funkcji/metody przypisanej jako funkcja tłumaczenia formatu. I nie zapominaj, że musisz mieć specyfikator formatu na klucz, jak wyżej.

Aktualizacja 2

Po swoimi dodatkowymi uwagami co trzeba użyć jest funkcją, a nie makro, wraz z atrybutem format, dla których kakao zapewnia wygodny NS_FORMAT_FUNCTION(f,a) makro. Ten atrybut informuje kompilator, że funkcja jest formatująca, wartość f jest numerem ciągu formatu, a a jest numerem pierwszego argumentu do formatu. Daje deklarację funkcji:

NSString *GetLocalStr(NSString *key, ...) NS_FORMAT_FUNCTION(1,2); 

oraz definicji (zakładając ARC):

NSString *GetLocalStr(NSString *key, ...) 
{ 
    va_list args; 
    va_start(args, key); 
    NSString *format = [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil]; 
    NSString *result = [[NSString alloc] initWithFormat:format arguments:args]; 
    va_end (args); 
    return result; 
} 

(która jest zasadniczo taka sama jak @-Live'S).

zastosowań tego będą sprawdzane w odpowiedni sposób, na przykład:

int x; 
... 
NSString *s1 = GetLocalStr(@"name = %d", x); // OK 
NSString *s2 = GetLocalStr(@"name = %d"); // compile warning - More '%" conversions than data arguments 
NSString *s3 = GetLocalStr(@"name", x);  // compile warning - Data argument not used by format string 
NSString *s4 = GetLocalStr(@"name");   // OK 
+0

Którego kompilatora i SDK używasz? -localizedStringForKey: value: table: powinno być już opatrzone adnotacją NS_FORMAT_ARGUMENT, więc dodatkowa funkcja localize() nie powinna być konieczna. –

+0

@GregParker - Dang, powtórzyłem błąd OP, założyłem, że to przez brak atrybutu, dodany w funkcji i to naprawiło. Okazuje się, że popełniłem swój własny błąd, zaktualizowałem odpowiedź. Dzięki. – CRD

+0

@CRD - dziękuję za szczegółową odpowiedź - pomogło mi to bardziej wyjaśnić problem (zaktualizowałem pytanie). Wywołanie 'GetLocalStr (@" name ", @" Foo ")' działa dobrze, jednak 'GetLocalStr (@" name ")' jest tym co dawało ostrzeżenie. Nie widziałem żadnej różnicy w dodawaniu klucza formatowania do specyfikacji - wydawało mi się, że działa bez niego, ale może coś nie rozumiem. – divillysausages

5

Wariant ten nie daje ostrzeżenia (jak zawsze jest va_list):

NSString* GetLocalStr1(NSString *formatKey, ...) { 
    va_list ap; 
    va_start(ap, formatKey); 
    NSString * format = [[NSBundle mainBundle] localizedStringForKey:formatKey value:@"" table:nil]; 
    NSString *result = [[NSString alloc] initWithFormat:format arguments:ap]; 
    va_end (ap); 
    return [result autorelease]; 
} 

... 

__unused NSString * str = GetLocalStr1(@"name", @"Foo"); 
__unused NSString * str1 = GetLocalStr1(@"TestNoArgs"); 
__unused NSString * str2 = GetLocalStr1(@"TestArgs", @"Foo"); 

NSLog(@"%@", str); 
NSLog(@"%@", str1); 
NSLog(@"%@", str2); 

Wynik:

mam na imie Foo

TestNoArgs

Hello world Foo

To nie jest odpowiedź na pytanie, dokładnie, ale może pomóc uniknąć ostrzeżenia, aż zostanie znalezione rozwiązanie.