【问题标题】:Adopting os_log APIs while keeping backward compatibility在保持向后兼容性的同时采用 os_log API
【发布时间】:2017-12-14 18:56:36
【问题描述】:

我正在尝试向库中添加对新日志记录和活动跟踪 API 的支持,以便为尚未采用最新版本操作系统(iOS 或 macOS)的库用户保持向后兼容性.我正在为每个级别的日志记录定义自定义日志记录宏,然后为旧操作系统定义回退到NSLog。我已经完成了这项工作,但有一个问题。

如果您希望它们显示在日志输出中,新 API 要求您将任何非常量、非标量值显式标记为 public。这就是我的宏调用的样子:

UZKLogInfo("Reading file %{public}@ from archive", fileName);

使用包含 os_log 的 SDK(例如 iOS 10.0 或更高版本)可以正常编译,但是当我使用早期版本编译时,我的宏回退到 NSLog,我收到编译器警告:

在 os_log()/os_trace() 之外使用 'public' 格式说明符注释

打印出来的日志是这样的:

Reading file <decode: missing data> from archive

这是我的宏定义的简化版(只包括info的定义和条件的简化:

#if UNIFIED_LOGGING_SUPPORTED
    @import os.log;

    #define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);
#else // Fall back to regular NSLog
    #define UZKLogInfo(format, ...) NSLog(@format, ##__VA_ARGS__);
#endif

在备用情况下,有什么方法可以从format 中删除“{public}”文本(某种字符串替换?)?或者是否有另一种方法来支持新旧 API 而不会放弃我一直在日志中显示的信息级别?我需要使用宏(根据last year's WWDC session on the topic,否则我会丢失调用站点元数据。

【问题讨论】:

  • “某种字符串替换”听起来您已经回答了自己的问题。你看过NSString提供了哪些方法吗?
  • @CRD 但是我想要替换的不是NSString,而是来自源文件的文本,它被放入宏中——它不是内存中分配的对象。我不确定预处理器有哪些可用的工具,但我认为我没有办法使用NSString 上的方法。
  • 您的@formatNSString,不管该字符串是编程生成的还是文字,它仍然是NSString。您可能无法在编译期间更改字符串,但您可以在运行时更改它。您的宏包含对NSLog 的调用,它可以包含方法调用。
  • @CRD 现在我明白了你的建议,但我正在寻找编译时解决方案。不过,我认为添加 NSString 替换可以作为最后的手段。
  • 编译时解决方案是更改代码文件上的文件扩展名,例如.myExt,并在Xcode 中为.myExt 添加构建规则。然后,构建规则将.myExt 处理为.m,删除不需要的{public},然后编译器处理.m。构建规则是一个脚本,它可以调用任何命令行应用程序等来完成它的工作。 Apple 有(曾经有?)一个使用 Ruby 脚本预处理字符串文件的示例,您也许可以对其进行调整。或者您可以在宏中包含 NSString 方法,运行时成本可能微不足道。

标签: ios objective-c macos logging macros


【解决方案1】:

我选择在宏中执行NSString 替换,并将编译器警告作为其中的一部分进行抑制,因此可以针对每一行执行此操作,而不是针对整个文件或项目全局执行此操作。它看起来像这样:

#if UNIFIED_LOGGING_SUPPORTED
    @import os.log;

    #define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);

#else // Fall back to regular NSLog

    #define _removeLogFormatTokens(format) [@format stringByReplacingOccurrencesOfString:@"{public}" withString:@""]
    #define _stringify(a) #a
    #define _nsLogWithoutWarnings(format, ...) \
        _Pragma( _stringify( clang diagnostic push ) ) \
        _Pragma( _stringify( clang diagnostic ignored "-Wformat-nonliteral" ) ) \
        _Pragma( _stringify( clang diagnostic ignored "-Wformat-security" ) ) \
        NSLog(_removeLogFormatTokens(format), ##__VA_ARGS__); \
        _Pragma( _stringify( clang diagnostic pop ) )

    #define UZKLogInfo(format, ...) _nsLogWithoutWarnings(format, ##__VA_ARGS__);
#endif

它是这样称呼的:

UZKLogInfo("Message: %@", anObjectToLog);

【讨论】:

  • 我也一直在思考如何做到这一点。感谢分享。我正在考虑的一件事是做相反的事情 - 将 {public} 添加到 iOS 10+ 的日志格式中,而不是删除旧版本。这样我就不必查找和替换我已经拥有的所有日志语句......我们的日志数据都不会是“私有的”。我们会看看情况如何。
  • 我很想知道你的#if UNIFIED_LOGGING_SUPPORTED 逻辑实际上是什么。我不知道在编译时在库中进行这种检查的方法,因为支持基于设备上的 iOS 版本,对吧?
  • @manroe 是的,我正在对所有平台的操作系统版本进行分组。你可以在这里看到它:github.com/abbeycode/UnzipKit/commit/…
  • 感谢您的链接。我想这对您有用,因为您链接到的标头是公共的,因此 __IPHONE_OS_VERSION_MIN_REQUIRED &gt;= 100000 是针对 SDK 用户的应用程序设置而不是您的 SDK 构建设置进行评估的。而且,因为如果应用程序支持 iOS 8,您将不支持 iOS 10 设备上的 os_log。这是我想做的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 1970-01-01
  • 1970-01-01
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多