5

I'm using a macro to simplify returning localised strings, like so:

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

Basically, if you have an entry in a localisation Strings file like "name" = "My name is %@";, calling

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

will return the NSString @"My name is Foo"

When I run it however, like:

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

I get the "format string is not a string literal" warning. Even following the advice of the other answers on SO about this warning and replacing it with:

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

I still get the warning, and besides, it kind of defeats the point of the macro making life easier.

How can I get rid of the warning short of wrapping all the GetLocalStr calls in #pragma suppressors?

Edit 27/08

After running through CRD's answer and doing some more tests, it seems like I made a bad assumption on the error. To clarify:

Localisation Strings file:

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

Code:

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

The majority of my translations take no arguments, and those were the ones giving the warning, but I didn't make the connection until I read through CRD's answer.

I changed my single macro to two, like so:

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

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

And if I call each one separately, there's no warnings.

I'd like GetLocalStr to expand to either GetLocalStrNoArgs or GetLocalStrArgs depending on if any arguments were passed or not, but so far I've been having no luck (macros are not my strong suit :D).

I'm using sizeof(#__VA_ARGS__) to determine if there's any arguments passed - it stringifys the arguments, and if the size is 1, it's empty (i.e. `\0'). Perhaps it's not the most ideal method, but it seems to work.

If I rewrite my GetLocalStr macro to:

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

I can use it, but I still get warnings everywhere it's used and there's no arguments passed, while something like

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

won't compile. How can I get my GetLocalStr macro to expand properly?

4

2 回答 2

5

此变体不产生警告(因为总是有 a 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);

结果:

我叫福

测试无参数

你好世界Foo

它不能准确回答问题,但可能会帮助您避免警告,直到找到解决方案。

于 2013-08-27T11:28:11.570 回答
5

Clang 和 GCC 编译器检查格式字符串和提供的参数是否符合,如果格式字符串不是文字,它们将无法执行此操作 - 因此当您从包中获取格式字符串时会看到错误消息。

为了解决这个问题,有一个属性format_arg(n)( docs ) 来标记采用格式字符串的函数;以某种方式改变它而不改变实际的格式说明符,例如翻译它;然后返回它。NS_FORMAT_ARG(n)Cocoa为这个属性提供了方便的宏。

要解决您的问题,您需要做两件事:

  1. 在指定此属性的函数中结束调用NSBundle;和

  2. 更改您的“密钥”以包含格式说明符。

其次,您的字符串文件应包含:

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

因此键具有与结果相同的格式说明符(如果您需要为特定语言重新排序说明符,则使用位置格式说明符)。

现在定义一个简单的函数来进行查找,将其归为格式转换函数。请注意,我们将其标记为static inline,使用宏NS_INLINE作为提示编译器将其内联到您的宏扩展中;允许您将其static包含在多个文件中而不会发生符号冲突:

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

你的宏变成:

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

现在当你:

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

您将获得本地化格式字符串和格式检查。

更新

在 Greg 发表评论后,我回去检查了 - 我已经复制了您的错误,因此认为它归结为缺少属性。然而,正如 Greg 指出的那样,localizedStringForKey:value:table:已经具有该属性,那么为什么会出现错误呢?我在重现您的错误时心不在焉地做的是:

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

并且编译器指向定义而不是那行 - 我应该发现编译器误导了我。

那么,这让你何去何从?也许你做过类似的事情?关键是格式字符串必须是文字或作为格式转换函数的函数/方法的结果。并且不要忘记,您还必须具有上述密钥的格式说明符。

更新 2

在您的附加注释之后,您需要使用的是函数,而不是宏以及format属性,Cocoa 为其提供了方便的NS_FORMAT_FUNCTION(f,a)宏。该属性通知编译器该函数是一个格式化函数,其值f是格式字符串a的编号,并且是格式的第一个参数的编号。这给出了函数声明:

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

和定义(假设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;
}

(这与@A-Live 的基本相同)。

将适当检查其用途,例如:

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
于 2013-08-26T19:51:44.917 回答