考虑以下示例:
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
转换为CVarArg
,创建一个Array<CVarArg>
,并将其作为logDefault
传递给args
。
然后logDefault
调用os_log
,将其作为参数传递给Array<CVarArg>
。 ,这是您代码中的错误。 该错误非常细微。问题在于os_log
不接受Array<CVarArg>
参数; os_log
本身比CVarArg
可变。因此,Swift将args
(一种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
使用废话Array<CVarArg>
格式化其第一个元素%f
(是0.000000
的一种),然后碰巧将logDefault
作为输出。 (我说“有点”,因为这里有些微妙之处,我稍后会解释。)因此,
Array<CVarArg>
将传入的os_log
作为可能的各种可变参数之一传递给logDefault
,但是您实际上想要Array<CVarArg>
要做的是将该传入的os_log
作为整个可变参数的传递给v
,而无需重新包装。在其他语言中,这有时称为“参数展开”。对于您来说可悲的是,Swift还没有任何用于参数扩展的语法。在Swift-Evolution(in this thread, for example)中已经讨论了多次,但是目前还没有解决方案。
解决此问题的常用方法是寻找一个将已 bundle 的可变参数作为单个参数的伴随函数。通常,同伴在函数名称上添加了
printf
。例子:vprintf
(可变)和va_list
(采用Array<CVarArg>
,C等于NSLog
)NSLogv
(可变)和va_list
(采用-[NSString initWithFormat:]
)-[NSString WithFormat:arguments:]
(可变)和va_list
(采用os_logv
)因此,您可能会去寻找
os_log
。可悲的是,您找不到一个。 os_log
没有记录有预 bundle 参数的伴侣。此时,您有两个选择:
%@
的方法,因为根本没有做到这一点的好方法,或者说%f
而不是%@
。但是请注意,您的消息字符串中只能有一个os_log
(没有其他格式说明符),因为您只向os_logv
传递了一个参数。输出看起来像这样:2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
您也可以在https://bugreport.apple.com上请求一个
os_log
函数来提交一个增强请求雷达,但是您不希望它很快就实现。就是这样了。做这两个事情之一,也许要提起雷达,然后继续生活。严重地。在这里停止阅读。这行之后没有什么好处。
好吧,你一直在读书。让我们来看看
os_log
的背后。事实证明,Swift os_log
函数的实现是public Swift source code的一部分:事实证明,是的
_swift_os_log
版本,称为withVaList
,它带有预先 bundle 的参数。 Swift包装器使用Array<CVarArg>
(已记录)将va_list
转换为_swift_os_log
,并将其传递给_swift_os_log
,后者本身也是public Swift source code的一部分。我不会在这里引用它的代码,因为它很长,我们实际上不需要查看它。无论如何,即使没有记录,我们实际上也可以调用
os_log
。我们基本上可以复制logDefault
的源代码并将其转换为您的os_log
函数: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
我会推荐这种解决方案吗?号 hell 号。
Array<CVarArg>
的内部是实现细节,在Swift的 future 版本中可能会更改。因此,不要像这样依靠他们。但是无论如何,在幕后找东西还是很有趣的。最后一件事。编译器为什么不提示将
CVarArg
转换为%@
?为什么Kamran的建议(使用Array
)起作用?事实证明,这些问题的答案是相同的:这是因为
Array
可“桥接”到Objective-C对象。具体来说:_ObjectiveCBridgeable
conform to the Array
protocol.通过返回NSArray
来实现Array
与Objective-C的桥接。 CVarArg
conform to the withVaList
protocol。 CVarArg
函数向convert itself to its _cVarArgEncoding
询问每个_cVarArgEncoding
。 _ObjectiveCBridgeable
和CVarArg
的类型,Array
返回桥接的Objective-C对象。 CVarArg
与Array<CVarArg>
的一致性意味着编译器不会提示(默默地)将CVarArg
转换为Array<CVarArg>
并将其粘贴到另一个args as CVarArg
中。 这种静默转换通常可能是一个错误(就像您的情况一样),因此编译器对其进行警告是合理的,并允许您使用显式强制转换(例如ojit_code)使警告静默。如果需要,可以在https://bugs.swift.org上提交错误报告。
关于ios - 为什么包装os_log()导致double不能正确记录?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50937765/