2013-08-26 4 views
5

Sto utilizzando una macro per semplificare il ritorno stringhe localizzate, in questo modo:Objective-C: "stringa di formato non è una stringa letterale (potenzialmente insicuro)" avvertimento con la macro

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

In sostanza, se si dispone di una voce in una localizzazione stringhe di file come "name" = "My name is %@";, chiamando

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

restituirà il NSString @"My name is Foo"

quando l'eseguo tuttavia, come:

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

Viene visualizzato l'avviso "Stringa di formato non è una stringa letterale". Anche seguendo i consigli delle altre risposte su SO su questo avviso e la sua sostituzione con:

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

ancora ottengo l'avvertimento, e, inoltre, che tipo di sconfitte più facile il punto della macro vita making.

Come si può eliminare l'avviso a corto di avvolgimento di tutte le chiamate GetLocalStr nei soppressori #pragma?

Modifica 27/08

Dopo che attraversa la risposta di CRD e fare qualche test, sembra che ho fatto una cattiva ipotesi sull'errore. Per chiarire:

localizzazione stringhe di file:

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

Codice:

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

La maggior parte delle mie traduzioni prendere nessun argomento, e quelli erano quelli che danno l'avvertimento, ma Non ho effettuato la connessione finché non ho letto la risposta di CRD.

ho cambiato la mia singola macro a due, in questo modo:

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

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

E se io chiamo ciascuno separatamente, non c'è nessun avviso.

mi piacerebbe GetLocalStr ad espandersi a uno o GetLocalStrNoArgsGetLocalStrArgs a seconda se tutti gli argomenti sono stati passati o no, ma finora ho avuto fortuna (le macro non sono il mio forte: D).

Sto usando sizeof(#__VA_ARGS__) per determinare se ci sono degli argomenti passati: stringe gli argomenti, e se la dimensione è 1, è vuoto (cioè "\ 0"). Forse non è il metodo più ideale, ma sembra funzionare.

Se riscrivo la mia GetLocalStr macro a:

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

posso usarlo, ma ho ancora ottenere avvertimenti ovunque viene utilizzato e non c'è argomenti passati, mentre qualcosa come

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

vinto' t compilare. Come posso ottenere la macro GetLocalStr per espandersi correttamente?

+0

'@" nome "= @" Il mio nome è% @ ";' - i valori letterali non sono assegnabili, non verranno compilati. 'NSString * format = @" Il mio nome è% @ "; NSString * str = GetLocalStr (formato, @ "Foo"); NSLog (@ "% @", str); 'non lancia alcun avvertimento (mi aspettavo comunque solo alla definizione di' GetLocalStr'), potresti voler correggere le virgolette e fornire maggiori dettagli su dove apparirà l'avviso. –

+1

È possibile creare una categoria NSString per farlo per voi. –

+0

@ A-Live il nome '@" "= @" Il mio nome è% @ "' la riga si trova in un file di localizzazione Strings, quindi tecnicamente dovrebbe essere solo "name" = "Il mio nome è% @" - scusa se quello non era chiaro L'avviso è contrassegnato su 'NSString * str = GetLocalStr (...);' linea – divillysausages

risposta

5

Il Clang & I compilatori GCC controllano che le stringhe di formato e gli argomenti forniti siano conformi, non possono farlo se la stringa di formato non è letterale, quindi il messaggio di errore viene visualizzato mentre si sta ottenendo la stringa di formato dal pacchetto.

Per risolvere questo problema è disponibile un attributo, format_arg(n) (docs), per contrassegnare le funzioni che accettano una stringa di formato; alteralo in qualche modo senza cambiando gli specificatori di formato attuali, ad esempio traducilo; e poi restituirlo. Cocoa fornisce la comoda macro NS_FORMAT_ARG(n) per questo attributo.

Per risolvere il problema è necessario fare due cose:

  1. Avvolgere la chiamata a NSBundle in una funzione con questo attributo specificato; e

  2. Modificare la "chiave" per includere gli specificatori di formato.

In secondo prima, il file stringhe deve contenere:

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

in modo che il tasto ha lo stesso identificatori di formato come il risultato (se avete bisogno di riordinare i progettisti per una determinata lingua si utilizza il formato posizionale prescrittori).

Definire ora una funzione semplice per eseguire la ricerca, attribuendola come funzione di traduzione del formato. Nota lo contrassegniamo come static inline, utilizzando la macro NS_INLINE come suggerimento per il compilatore per incorporarlo nell'espansione macro; il static consente di includere in più file, senza scontri di simboli:

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

E la macro diventa:

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

Ora, quando si:

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

otterrete sia il localizzato formattare il controllo della stringa e del formato.

Aggiornamento

Dopo il commento di Greg sono tornato e controllato - avevo riprodotto il vostro errore e così assunto era giù ad un attributo mancante. Tuttavia, come sottolinea Greg localizedStringForKey:value:table: ha già l'attributo, quindi perché l'errore?Quello che avevo distrattamente fatto nel riprodurre l'errore era:

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

e il compilatore indicò la definizione macro e non quella linea - avrei individuato il compilatore mi era fuorviante.

Quindi, dove ti lascia? Forse hai fatto qualcosa di simile? La chiave è che una stringa di formato deve essere letterale o il risultato di una funzione/metodo attribuito come funzione di traduzione di un formato. E non dimenticare, devi avere anche l'identificatore di formato per la tua chiave come sopra.

Update 2

Dopo che i vostri commenti aggiuntivi che cosa è necessario utilizzare la funzione è, piuttosto che una macro, insieme con l'attributo format, per i quali cacao fornisce la comoda NS_FORMAT_FUNCTION(f,a) macro. Questo attributo informa il compilatore che la funzione è di formattazione, il valore di f è il numero della stringa di formato e a è il numero del primo argomento del formato. Questo dà la dichiarazione di funzione:

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

e la definizione (supponendo 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; 
} 

(che è essenzialmente lo stesso come @ A-Live).

Usi di questo saranno controllati in modo appropriato, per esempio:

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

Quale compilatore e SDK stai usando? -localizedStringForKey: valore: table: dovrebbe già essere annotato con NS_FORMAT_ARGUMENT in modo che la funzione extra localize() non sia necessaria. –

+0

@GregParker - Dang, ho riprodotto l'errore dell'OP, supponendo che fosse dovuto alla mancanza dell'attributo, aggiunto nella funzione, e che l'ha risolto. Risulta che ho fatto il mio errore, risposta aggiornata. Grazie. – CRD

+0

@CRD - grazie per la risposta dettagliata - mi ha aiutato a chiarire di più il problema (ho aggiornato la domanda). Chiamare 'GetLocalStr (@" nome ", @" Foo ")' funziona bene, tuttavia 'GetLocalStr (@" nome ")' è ciò che stava dando l'avviso. Non ho visto alcuna differenza con l'aggiunta dello specificatore di formato alla chiave - sembrava funzionare bene senza di essa, ma forse sto fraintendendo qualcosa – divillysausages

5

Questa variante produce nessun avviso (come c'è sempre un 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); 

risultati:

Mi chiamo Foo

TestNoArgs

Ciao mondo Foo

Essa non risponde alla domanda esattamente, ma potrebbe aiutare a evitare gli avvisi fino a trovare la soluzione.