NSLogv C 字符串编码

Dar*_*rov 5 objective-c ios

我使用以下方法编写了一个小型日志记录包装器NSLogv

\n\n
void MyLog(const char* format, ...) {\n    va_list vargs;\n    va_start(vargs, format);\n    NSString* formatStr = [NSString stringWithUTF8String:format];\n    NSLogv(formatStr, vargs);\n    va_end(vargs);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我可以这样使用:

\n\n
MyLog("%d - %s", 123, "ABCD");\n
Run Code Online (Sandbox Code Playgroud)\n\n

我遇到的问题是当我使用标准 ASCII 范围之外的字符时:

\n\n
MyLog("%d - %s", 123, "\xd0\x90\xd0\x91\xd0\x92\xd0\x93");\n
Run Code Online (Sandbox Code Playgroud)\n\n

NSLogv无法正确编码这些字符:

\n\n
2019-10-01 11:10:30.890346+0300 TestApp[86349:7051788] 123 - \xe2\x80\x93\xc3\xaa\xe2\x80\x93\xc3\xab\xe2\x80\x93\xc3\xad\xe2\x80\x93\xc3\xac\n
Run Code Online (Sandbox Code Playgroud)\n\n

在保持辅助方法的可变参数签名的同时对这些字符进行编码的正确方法是什么?

\n\n

PS 在X86_64模拟器和ARM64设备上都尝试过

\n\n

如果我将 C 字符串转换为 UTF16,那么它会按预期工作:

\n\n
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;\nstd::u16string value16 = convert.from_bytes("\xd0\x90\xd0\x91\xd0\x92\xd0\x93");\nMyLog("%d - %S", 123, value16.c_str());\n
Run Code Online (Sandbox Code Playgroud)\n

CRD*_*CRD 2

您发现了 Objective-C 中格式化的一个奇怪“功能”,应该访问Apple 的反馈助手并报告它。

\n\n

那么发生了什么?嗯,它与可变参数函数完全无关,也与NSLogv 本身无关。NSLog相反,它与、NSLogvstringWithFormat:等人使用的底层 Objective-C 格式化代码以及格式字符串本身的类型有关......

\n\n

这是“功能”的简单演示:

\n\n
- (void)demo\n{\n   char *sample = "\xd0\x90\xd0\x91\xd0\x92\xd0\x93"; // This will be UTF-8 encoded\n\n   // use %p to show address, %s to show string, \\n as printf doesn\'t add one\n   char *cFormat = "%p - %s\\n";\n   NSString *nsFormat = @"%p - %s\\n"; // produces an __NSCFConstantString\n   NSString *convertedFormat = [NSString stringWithUTF8String:cFormat]; // produces an __NSCFString\n\n   printf(cFormat, sample, sample); // works\n   NSLog(convertedFormat, sample, sample); // fails with __NSCFString\n   NSLog(nsFormat, sample, sample); // works with __NSCFConstantString\n\n   NSLog(@"formats equal: %s", [convertedFormat isEqualToString:nsFormat] ? "yes" : "no"); // __NSCFString & __NSCFConstantString are equal\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

运行它,控制台将显示如下内容:

\n\n
0x1000013f8 - \xd0\x90\xd0\x91\xd0\x92\xd0\x93\n2019-10-01 10:25:48.222537+0100 demo[8435:1431874] 0x1000013f8 - \xe2\x80\x93\xc3\xaa\xe2\x80\x93\xc3\xab\xe2\x80\x93\xc3\xad\xe2\x80\x93\xc3\xac\n\n2019-10-01 10:25:48.222560+0100 demo[8435:1431874] 0x1000013f8 - \xd0\x90\xd0\x91\xd0\x92\xd0\x93\n2019-10-01 10:25:48.222582+0100 demo[8435:1431874] formats equal: yes\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,C 库printf可以工作,NSLog常量格式可以工作,但从a 转换的格式则不行 ,但后两种格式比较相等...还要注意,在失败的情况下会添加额外的换行符。NSStringNSLogNSStringchar *NSLog

\n\n

错误的输出\xe2\x80\x93\xc3\xaa\xe2\x80\x93\xc3\xab\xe2\x80\x93\xc3\xad\xe2\x80\x93\xc3\xac与 Xcode 为字符串的内存字节显示的参数字符串的解释相同。因此,格式字符串的基础类型决定了如何解释参数字符串的基础字节......

\n\n

如此好奇的“功能”让人怀疑它是否是出于某种原因而设计的,或者我们是否错过了显而易见的东西......也许其他人可以启发我们,但除非他们这样做,否则我们称其为(好奇的)漏洞!

\n\n

解决方法

\n\n

正如上面的演示所示,使用 C 库格式函数是可行的,因此,如果您愿意NSLog在每个大纲上丢失 \ 的序言,您可以在函数中使用其中之一:

\n\n
void MyLog(const char *format, ...)\n{\n   va_list vargs;\n   va_start(vargs, format);\n   vprintf(format, vargs);\n   va_end(vargs);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您希望保留NSLog输出,您可以使用 C 库的等效项之一stringWithFormat:,这是函数的一个版本,它动态地为格式化的 C 字符串分配所需的空间,然后释放它(ARC不会为你做这些!):

\n\n
void MyLog(const char *format, ...)\n{\n   va_list vargs;\n   va_start(vargs, format);\n   char *output;\n   vasprintf(&output, format, vargs);\n   NSLog(@"%s", output);\n   free(output);\n   va_end(vargs);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

该“功能”在最新的 Xcode 11 和 macOS Catalina Beta 中仍然存在,因此请前往Apple 的反馈助手报告该问题。

\n\n

华泰

\n