Ios 目标-C:“;格式字符串不是字符串文字(可能不安全)";宏警告

Ios 目标-C:“;格式字符串不是字符串文字(可能不安全)";宏警告,ios,objective-c,string,macros,Ios,Objective C,String,Macros,我使用宏来简化返回本地化字符串的过程,如下所示: #define GetLocalStr(key, ...) \ [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__] #define GetLocalStrNoArgs(key) \ [[NSBundle mainBundle] localizedString

我使用宏来简化返回本地化字符串的过程,如下所示:

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

#define GetLocalStrArgs(key, ...) \
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__]
基本上,如果本地化字符串文件中有一个条目,如
“name”=“My name is%@”,正在呼叫

GetLocalStr( @"name", @"Foo" );
将返回NSString
@“我的名字是Foo”

但是,当我运行它时,就像:

NSString * str = GetLocalStr( @"name", @"Foo" );
我得到了“格式字符串不是字符串文字”的警告。即使按照其他答案中关于此警告的建议,并将其替换为:

NSString * str = [NSString stringWithFormat:@"%@", GetLocalStr( @"name", @"Foo" )];
我仍然得到警告,而且,这有点违背了宏观经济让生活更轻松的观点

除了将所有
GetLocalStr
调用包装在#pragma抑制器中之外,我如何摆脱警告

编辑27/08

在浏览了CRD的答案并做了更多的测试之后,我似乎对错误做出了错误的假设。澄清:

本地化字符串文件:

代码:

我的大多数翻译都没有任何论据,这些都是给出警告的,但直到我通读了CRD的答案,我才把它们联系起来

我将单个宏更改为两个,如下所示:

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

#define GetLocalStrArgs(key, ...) \
    [NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil], ##__VA_ARGS__]
如果我分别给每个人打电话,就不会有任何警告

我希望
GetLocalStr
扩展到
GetLocalStrNoArgs
GetLocalStrArgs
,这取决于是否传递了任何参数,但到目前为止我一直运气不佳(宏不是我的强项:d)

我使用
sizeof(#u_uva_ARGS_uu)
来确定是否传递了任何参数-它将参数字符串化,如果大小为1,则为空(即“\0”)。也许这不是最理想的方法,但似乎有效

如果我将我的
GetLocalStr
宏重写为:

#define GetLocalStr(key,...) (sizeof(#__VA_ARGS__) == 1) ? GetLocalStrNoArgs(key) : GetLocalStrArgs(key,##__VA_ARGS__)
我可以使用它,但在使用它的任何地方都会收到警告,并且没有传递任何参数,而

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

不会编译。如何使我的
GetLocalStr
宏正确展开?

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

为了解决这个问题,有一个属性,
format_arg(n)
(),用于标记采用格式字符串的函数;在不改变实际格式说明符的情况下以某种方式对其进行修改,例如翻译;然后把它还给我。Cocoa为此属性提供了方便的宏
NS\u FORMAT\u ARG(n)

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

  • 将对
    NSBundle
    的调用包装到指定了此属性的函数中;及

  • 更改“键”以包含格式说明符

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

    "name %@" = "My name is %@"
    
    因此,键具有与结果相同的格式说明符(如果需要对特定语言的说明符重新排序,则使用位置格式说明符)

    现在定义一个简单的函数来执行查找,并将其指定为格式转换函数。注意:我们将其标记为
    静态内联
    ,使用宏
    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指出的那样,
    localizedStringForKey:value:table:
    已经具有该属性,那么为什么会出现错误呢?我心不在焉地再现了你的错误:

    NSLog( GetLocalStr( @"name %@", @"Foo" ) );
    
    编译器指向宏定义而不是那一行-我应该发现编译器误导了我

    那你现在的处境如何?也许你也做过类似的事情?关键是格式字符串必须是文本或作为格式转换函数属性化的函数/方法的结果。别忘了,您的密钥还必须具有如上所述的格式说明符

    更新2

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

    NSString *GetLocalStr(NSString *key, ...) NS_FORMAT_FUNCTION(1,2);
    
    以及定义(假设为圆弧):

    (本质上与@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
    

    此变体不产生警告(因为始终存在
    va_列表
    ):

    结果:

    我叫福

    TestNoArgs

    你好,福娃


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

    @“name”=@“My name is%@”-文字不可分配,无法编译<代码>NSString*格式=@“我的名字是%@”;NSString*str=GetLocalStr(格式为@“Foo”);NSLog(@“%@”,str)
    不会抛出任何警告(我只在
    GetLocalStr
    定义中预期),您可能希望修复引号并提供有关警告出现位置的更多详细信息。您可以创建一个NSString类别来为您执行此操作。@a-Live
    @“name”=@“My name is%@”
    行位于字符串本地化文件中,所以从技术上讲,它应该是“name”=“My name is%@”-如果不清楚的话,很抱歉。警告标记在
    NSString*str=GetLocalStr(…)line@JoshCaswell我知道关于这场战争的其他问题
    
    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
    
    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);