2013-08-26 24 views
5

Tôi đang sử dụng một macro để đơn giản hóa trở dây cục bộ, như vậy:Objective-C: "chuỗi định dạng không phải là một chuỗi chữ (có khả năng không an toàn)" cảnh báo với vĩ mô

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

Về cơ bản, nếu bạn có một mục trong một Strings nội địa hóa tập tin như "name" = "My name is %@";, gọi

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

sẽ trả lại NSString @"My name is Foo"

Khi tôi chạy nó tuy nhiên, như:

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

Tôi nhận được cảnh báo "chuỗi định dạng không phải là chuỗi chữ". Thậm chí theo lời khuyên của các câu trả lời khác trên SO về cảnh báo này và thay thế nó bằng:

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

tôi vẫn nhận được cảnh báo, và bên cạnh đó, nó loại đánh bại điểm của đời sống làm vĩ mô dễ dàng hơn.

Tôi làm cách nào để loại bỏ cảnh báo ngắn gọn khi gói tất cả các cuộc gọi GetLocalStr trong #pragma suppressors?

Sửa 27/08

Sau khi chạy qua câu trả lời CRD và làm một số xét nghiệm hơn, nó có vẻ như tôi đã thực hiện một giả định xấu về lỗi. Để làm rõ:

Localization Strings file:

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

Code:

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

Đa số các bản dịch của tôi không có đối số, và những người là những người đưa ra các cảnh báo, nhưng Tôi đã không thực hiện kết nối cho đến khi tôi đọc qua câu trả lời của CRD.

tôi đã thay đổi vĩ mô duy nhất của tôi đến hai, như vậy:

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

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

Và nếu tôi gọi nhau một cách riêng biệt, không có cảnh báo.

Tôi muốn GetLocalStr để mở rộng thành GetLocalStrNoArgs hoặc GetLocalStrArgs tùy thuộc vào nếu bất kỳ đối số nào được chuyển hay không, nhưng cho đến giờ tôi không có may mắn (macro không phù hợp với tôi: D).

Tôi đang sử dụng sizeof(#__VA_ARGS__) để xác định xem có bất kỳ đối số nào được chuyển hay không - nó xâu chuỗi các đối số và nếu kích thước là 1, số đó trống (tức là `\ 0 '). Có lẽ nó không phải là phương pháp lý tưởng nhất, nhưng có vẻ như nó hoạt động.

Nếu tôi viết lại tôi GetLocalStr vĩ mô để:

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

tôi có thể sử dụng nó, nhưng tôi vẫn nhận được cảnh báo ở khắp mọi nơi nó được sử dụng và không có lý lẽ trôi qua, trong khi một cái gì đó giống như

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

won' t biên dịch. Làm cách nào để có được macro GetLocalStr của tôi để mở rộng chính xác?

+0

'@" name "= @" Tên tôi là% @ ";' - chữ cái không được gán, nó sẽ không biên dịch. 'NSString * format = @" Tên tôi là% @ "; NSString * str = GetLocalStr (định dạng, @ "Foo"); NSLog (@ "% @", str); 'không ném bất kỳ cảnh báo nào (tôi chỉ mong đợi nó ở định nghĩa' GetLocalStr'), bạn có thể muốn sửa các trích dẫn và cung cấp thêm chi tiết về nơi cảnh báo xuất hiện. –

+1

Bạn có thể tạo Danh mục NSString để làm điều đó cho bạn. –

+0

@ A-Live tên '@" "= @" Tên tôi là% @ "' dòng trong tệp Bản địa hóa chuỗi, vì vậy về mặt kỹ thuật, nó chỉ nên là "name" = "Tên tôi là% @" - xin lỗi nếu điều đó không rõ ràng. Cảnh báo được gắn cờ trên 'NSString * str = GetLocalStr (...);' line – divillysausages

Trả lời

5

Clang & Trình biên dịch GCC kiểm tra chuỗi định dạng và đối số được cung cấp phù hợp, chúng không thể làm điều này nếu chuỗi định dạng không phải là chữ - do đó thông báo lỗi bạn thấy khi bạn đang lấy chuỗi định dạng từ gói.

Để giải quyết vấn đề này, có một thuộc tính, format_arg(n) (docs), để đánh dấu các hàm có chuỗi định dạng; thay đổi nó theo một cách nào đó mà không cần thay đổi các định dạng định dạng thực tế, ví dụ: dịch nó; và sau đó trả lại. Ca cao cung cấp macro tiện lợi NS_FORMAT_ARG(n) cho thuộc tính này.

Để khắc phục vấn đề của bạn, bạn cần phải làm hai việc:

  1. Tổng kết các cuộc gọi đến NSBundle trong một hàm với thuộc tính này quy định; và

  2. Thay đổi "khóa" của bạn để bao gồm các thông số định dạng.

Thứ hai đầu tiên, tập tin chuỗi của bạn nên bao gồm:

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

để chìa khóa có định dạng specifiers tương tự như kết quả (nếu bạn cần phải sắp xếp lại các specifiers cho một ngôn ngữ đặc biệt mà bạn sử dụng định dạng vị trí specifiers).

Bây giờ, hãy xác định một hàm đơn giản để thực hiện tra cứu, gán nó làm hàm dịch thuật định dạng. Lưu ý chúng tôi đánh dấu nó là static inline, sử dụng macro NS_INLINE làm gợi ý cho trình biên dịch cho cả hai nội tuyến vào macro mở rộng của bạn; các static cho phép bạn bao gồm nó trong nhiều file mà không cần đụng độ biểu tượng:

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

Và macro của bạn trở thành:

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

Bây giờ khi bạn:

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

Bạn sẽ nhận được cả cục bộ định dạng chuỗi và kiểm tra định dạng.

Cập nhật

Sau khi bình luận của Greg tôi đã đi lại và kiểm tra - Tôi đã sao chép lỗi của bạn và do đó giả nó đã xuống đến một thuộc tính thiếu. Tuy nhiên như Greg chỉ ra localizedStringForKey:value:table: đã có thuộc tính, vậy tại sao lỗi?Những gì tôi đã lơ đãng thực hiện trong tái tạo lỗi của bạn là:

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

và trình biên dịch chỉ vào định nghĩa vĩ mô và không dòng đó - tôi nên đã phát hiện các trình biên dịch đã gây hiểu lầm tôi.

Vậy điều đó khiến bạn ở đâu? Có lẽ bạn đã làm một cái gì đó tương tự? Điều quan trọng là một chuỗi định dạng phải là một chữ hoặc kết quả của một hàm/phương thức được gán là một hàm dịch định dạng. Và đừng quên, bạn cũng phải có trình định dạng định dạng cho khóa của bạn như trên.

Cập nhật 2

Sau ý kiến ​​bổ sung của bạn những gì bạn cần sử dụng là chức năng, chứ không phải là một vĩ mô, cùng với format thuộc tính, mà Cocoa mang lại lợi NS_FORMAT_FUNCTION(f,a) vĩ mô. Thuộc tính này thông báo cho trình biên dịch rằng hàm là một định dạng, giá trị của f là số của chuỗi định dạng và a là số của đối số đầu tiên cho định dạng. Điều này cho phép khai báo hàm:

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

và định nghĩa (giả sử 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; 
} 

(trong đó chủ yếu là giống như @ A-Live).

Sử dụng này sẽ được kiểm tra một cách thích hợp, ví dụ:

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

Bạn đang sử dụng trình biên dịch và SDK nào? -localizedStringForKey: value: table: nên được chú thích với NS_FORMAT_ARGUMENT, vì vậy hàm localize() không cần thiết. –

+0

@GregParker - Dang, tôi đã sao chép lỗi của OP, cho rằng đó là do thiếu thuộc tính, được thêm vào trong hàm và đã sửa nó. Hóa ra tôi đã mắc lỗi của riêng tôi, câu trả lời cập nhật. Cảm ơn. – CRD

+0

@CRD - cảm ơn câu trả lời chi tiết - nó giúp tôi làm rõ vấn đề hơn (tôi đã cập nhật câu hỏi). Gọi 'GetLocalStr (@" name ", @" Foo ")' hoạt động tốt, tuy nhiên 'GetLocalStr (@" name ")' là cái đã đưa ra cảnh báo. Tôi không thấy bất kỳ sự khác biệt nào với việc thêm định dạng specifier vào khóa - nó dường như hoạt động tốt mà không có nó, nhưng có lẽ tôi đang hiểu nhầm một cái gì đó – divillysausages

5

biến này tạo ra không có cảnh báo (như luôn có một 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); 

Kết quả:

tên tôi là Foo

TestNoArgs

Hello world Foo

Nó không trả lời câu hỏi một cách chính xác nhưng có thể giúp bạn tránh được cảnh báo cho đến khi dung dịch được tìm thấy.

Các vấn đề liên quan