ios - 为什么包装 os_log() 会导致无法正确记录双打?
问题描述
考虑以下示例:
import Foundation
import os.log
class OSLogWrapper {
func logDefault(_ message: StaticString, _ args: CVarArg...) {
os_log(message, type: .default, args)
}
func testWrapper() {
logDefault("WTF: %f", 1.2345)
}
}
如果我创建一个新实例OSLogWrapper
并调用testWrapper()
let logger = OSLogWrapper()
logger.testWrapper()
我在 Xcode 控制台中得到以下输出:
2018-06-19 18:21:08.327979-0400 WrapperWTF[50240:548958] WTF: 0.000000
我已经检查了我能想到的所有内容,但我无法确定这里出了什么问题。浏览文档并没有产生任何帮助。
解决方案
编译器通过将每个参数转换为声明的可变参数类型,将它们打包成Array
该类型的一个,并将该数组传递给可变参数函数来实现可变参数参数。在 的情况下testWrapper
,声明的可变参数类型是CVarArg
,所以当testWrapper
调用时logDefault
,这就是幕后发生的事情:testWrapper
强制转换1.2345
为 a CVarArg
,创建一个Array<CVarArg>
,并将其传递给logDefault
as args
。
然后logDefault
调用os_log
,将其Array<CVarArg>
作为参数传递。这是您的代码中的错误。该错误非常微妙。问题是os_log
不需要Array<CVarArg>
争论。os_log
本身就是可变参数的CVarArg
。所以 Swift 将args
(an Array<CVarArg>
) 投射到,并将投射到另一个CVarArg
的棍子粘住。结构如下所示:CVarArg
Array<CVarArg>
Array<CVarArg> created in `logDefault`
|
+--> CVarArg (element at index 0)
|
+--> Array<CVarArg> (created in `testWrapper`)
|
+--> CVarArg (element at index 0)
|
+--> 1.2345 (a Double)
然后logDefault
将这个新的传递Array<CVarArg>
给os_log
. 所以你要求os_log
格式化它的第一个元素,它是(有点) an Array<CVarArg>
, using %f
,这是无意义的,你碰巧得到0.000000
了输出。(我说“有点”是因为这里有一些我稍后会解释的微妙之处。)
因此,logDefault
将其传入Array<CVarArg>
作为可能的许多可变参数之一传递给os_log
,但您真正想要logDefault
做的是将该传入Array<CVarArg>
作为整个可变参数集传递给os_log
,而不重新包装它。这有时在其他语言中被称为“争论飞溅”。
可悲的是,Swift 还没有任何用于参数飞溅的语法。在 Swift-Evolution 中已经不止一次讨论过这个问题(例如,在这个线程中),但目前还没有一个解决方案。
这个问题的通常解决方案是寻找一个伴随函数,它将已经捆绑的可变参数作为单个参数。通常,伴随v
程序在函数名称中添加了一个。例子:
printf
(variadic) andvprintf
(take ava_list
, C's 等价于Array<CVarArg>
)NSLog
(可变参数)和NSLogv
(取 ava_list
)-[NSString initWithFormat:]
(可变参数)和-[NSString WithFormat:arguments:]
(取 ava_list
)
所以你可能会去寻找一个os_logv
. 可悲的是,你不会找到一个。没有记录在案的伴侣os_log
需要预先捆绑的参数。
此时您有两个选择:
放弃
os_log
使用您自己的可变参数包装器进行包装,因为根本没有好方法可以做到这一点,或者接受卡姆兰的建议(在他对您问题的评论中)并使用
%@
而不是%f
. 但请注意,您的消息字符串中只能有一个%@
(并且没有其他格式说明符),因为您只将一个参数传递给os_log
. 输出如下所示:2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: ( "1.2345" )
您还可以在https://bugreport.apple.com上提交增强请求雷达,要求提供os_logv
功能,但您不应该期望它会很快实现。
就是这样了。做这两件事中的一件,也许归档一个雷达,然后继续你的生活。严重地。在这里停止阅读。这条线之后就没什么好说的了。
好吧,你一直在读。让我们窥视一下os_log
。事实证明 Swiftos_log
函数的实现是公开的 Swift 源代码的一部分:
@_exported import os @_exported import os.log import _SwiftOSOverlayShims @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) public func os_log( _ type: OSLogType, dso: UnsafeRawPointer = #dsohandle, log: OSLog = .default, _ message: StaticString, _ args: CVarArg...) { guard log.isEnabled(type: type) else { return } let ra = _swift_os_log_return_address() message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in // Since dladdr is in libc, it is safe to unsafeBitCast // the cstring argument type. buf.baseAddress!.withMemoryRebound( to: CChar.self, capacity: buf.count ) { str in withVaList(args) { valist in _swift_os_log(dso, ra, log, type, str, valist) } } } }
所以事实证明,有一个版本os_log
,称为_swift_os_log
,它采用预先捆绑的参数。Swift 包装器使用withVaList
(已记录)将 a 转换Array<CVarArg>
为 ava_list
并将其传递给_swift_os_log
,它本身也是公共 Swift 源代码的一部分。我不会在这里引用它的代码,因为它很长而且我们实际上不需要查看它。
无论如何,即使它没有记录,我们实际上可以调用_swift_os_log
. 我们基本上可以复制源代码,os_log
并把它变成你的logDefault
函数:
func logDefaultHack(_ message: StaticString, dso: UnsafeRawPointer = #dsohandle, _ args: CVarArg...) {
let ra = _swift_os_log_return_address()
message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
buf.baseAddress!.withMemoryRebound(to: CChar.self, capacity: buf.count) { str in
withVaList(args) { valist in
_swift_os_log(dso, ra, .default, .default, str, valist)
}
}
}
}
它有效。测试代码:
func testWrapper() {
logDefault("WTF: %f", 1.2345)
logDefault("WTF: %@", 1.2345)
logDefaultHack("Hack: %f", 1.2345)
}
输出:
2018-06-20 02:22:56.131875-0500 test[39313:6086331] WTF: 0.000000
2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
2018-06-20 02:22:56.132807-0500 test[39313:6086331] Hack: 1.234500
我会推荐这个解决方案吗?不,不。的内部os_log
是一个实现细节,可能会在 Swift 的未来版本中发生变化。所以不要像这样依赖他们。但无论如何,看看幕后花絮还是很有趣的。
最后一件事。为什么编译器不抱怨转换Array<CVarArg>
为CVarArg
? 为什么卡姆兰的建议(使用%@
)有效?
事实证明,这些问题有相同的答案:因为它Array
可以“桥接”到 Objective-C 对象。具体来说:
Foundation(在 Apple 平台上)使
Array
符合_ObjectiveCBridgeable
协议。它Array
通过返回一个NSArray
.该
withVaList
函数要求每个CVarArg
将自己转换为它的_cVarArgEncoding
._cVarArgEncoding
对于同时符合_ObjectiveCBridgeable
和的类型,默认实现CVarArg
返回桥接的 Objective-C 对象。Array
to的一致性CVarArg
意味着编译器不会抱怨(默默地)将 an 转换Array<CVarArg>
为 aCVarArg
并将其粘贴到 anotherArray<CVarArg>
中。
这种静默转换可能通常是一个错误(就像您的情况一样),因此编译器发出警告是合理的,并允许您使用显式强制转换(例如args as CVarArg
)使警告静音。如果需要,您可以在https://bugs.swift.org提交错误报告。
推荐阅读
- vue.js - q-stepper header-nav 仅在出现一个 q-step 时将自身向右对齐
- python - Pycharm jupyter-notebook 改变编码风格
- java - 如何发送数据java和html
- c# - 获取 WPF 中特定 DataGridTemplateColumn 的更新值
- python - 在 PYGAME 中单击图像时如何移动图像?
- citrus-framework - 如何为 citrus-http:client 标签动态参数化 citrus-context.xml 中的 request-url?
- xslt - 带有分隔符的字符串上的 XSLT 调用模板
- r - 从邻接矩阵不生成边
- javascript - 如何将数据从中间件传递到路由器?
- azure - Azure EventGrid 队列以编程方式将消息设置为永不过期