编译器通过将每个参数强制转换为声明的可变类型,将其打包为该类型的Array
,并将该数组传递给可变函数来实现可变参数.在testWrapper
的情况下,声明的变量类型是CVarArg
,所以当testWrapper
调用logDefault
时,这是在幕后发生的事情:testWrapper
将1.2345
转换为CVarArg
,创建Array<CVarArg>
,并将其作为args
传递给logDefault
.
然后logDefault
调用os_log
,将其作为参数传递给Array<CVarArg>
.This is the bug in your code.这个错误很微妙.问题是os_log
不接受Array<CVarArg>
的论点;os_log
本身就是CVarArg
的变量.因此,Swift 将args
(ANArray<CVarArg>
)铸造到CVarArg
,将CVarArg
铸造到anotherArray<CVarArg>
. struct 如下所示:
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
,而不需要重新包装它.在其他语言中,这有时被称为"参数散乱".
遗憾的是,Swift 还没有任何用于参数散乱的语法.在《快速进化》(Swift Evolution,in this thread, for example)一书中,人们不止一次讨论过这个问题,但目前还没有一个解决方案.
解决这个问题的通常方法是寻找一个伴随函数,该函数将已绑定的可变参数作为单个参数.通常情况下,伴随函数名中会添加一个v
.例如:
- 取
va_list
(a,C)的等效值
NSLog
(可变)和NSLogv
(取va_list
)
-[NSString initWithFormat:]
(可变)和-[NSString WithFormat:arguments:]
(取va_list
)
所以你可以go 找一个os_logv
.遗憾的是,你找不到.os_log
没有记录在案的附带文件可以接受预先绑定的参数.
此时,您有两个 Select :
放弃用你自己的变量包装器包装os_log
,因为根本没有好方法,或者
接受卡姆兰的建议(在他对你的问题的 comments 中),用%@
而不是%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
的引擎盖下窥视一下.事实证明,Swift os_log
功能的实现是public Swift source code:
@_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
(有文档记录)将Array<CVarArg>
转换为va_list
,并将其传递给_swift_os_log
,_swift_os_log
本身也是public Swift source code的一部分.我不想在这里引用它的代码,因为它很长,我们实际上不需要看它.
不管怎样,即使没有记录,我们实际上可以拨打_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
我能推荐这个解决方案吗?No.Hell No.os_log
的内部是一个实现细节,可能会在Swift的future 版本中发生变化.所以不要像这样依赖他们.但不管怎么说,从被子里看还是很有趣的.
最后一件事.为什么编译器不抱怨将Array<CVarArg>
转换为CVarArg
?为什么卡姆兰的建议(使用%@
)有效?
事实证明,这些问题都有相同的答案:这是因为Array
可以"桥接"到Objective-C对象.明确地:
这种静默转换可能通常是一个错误(就像在您的例子中一样),因此编译器应该对此发出警告,并允许您使用显式强制转换(例如args as CVarArg
)来静默警告.如果你愿意的话,你可以在https://bugs.swift.org点提交错误报告.