Mik*_*ers 25 logging ios swift
请考虑以下示例:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
如果我创建一个新的实例OSLogWrapper
并调用testWrapper()
let logger = OSLogWrapper()
logger.testWrapper()
Run Code Online (Sandbox Code Playgroud)
我在Xcode控制台中获得以下输出:
2018-06-19 18:21:08.327979-0400 WrapperWTF[50240:548958] WTF: 0.000000
Run Code Online (Sandbox Code Playgroud)
我已经检查了所有我能想到的东西,而且我无法对这里出了什么问题做出正面或反面.浏览文档并没有产生任何帮助.
谢谢您的帮助!
rob*_*off 58
编译器通过将每个参数转换为声明的可变参数类型,将它们打包成Array
该类型的一个,并将该数组传递给可变参数函数来实现可变参数.在这种情况下testWrapper
,声明的可变参数类型是CVarArg
,所以当testWrapper
调用时logDefault
,这是在封面下发生的事情:testWrapper
转换1.2345
为a CVarArg
,创建一个Array<CVarArg>
,并将其传递给logDefault
as args
.
然后logDefault
调用os_log
,将其Array<CVarArg>
作为参数传递给它.这是您的代码中的错误.这个bug非常微妙.问题是os_log
没有Array<CVarArg>
争论; os_log
本身是可变的CVarArg
.因此,斯威夫特施放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)
Run Code Online (Sandbox Code Playgroud)
然后logDefault
传递这个新Array<CVarArg>
的os_log
.所以你要求os_log
格式化它的第一个元素,即(某种)Array<CVarArg>
,使用%f
,这是无意义的,你碰巧得到0.000000
了输出.(我说"有点",因为这里有一些细微之处,我稍后会解释.)
因此,logDefault
将其传入Array<CVarArg>
作为潜在的多个可变参数之一传递给os_log
,但实际上您logDefault
要做的是将该传入Array<CVarArg>
作为整个可变参数集传递给os_log
,而不重新包装它.在其他语言中,这有时被称为"参数splatting".
可悲的是,Swift还没有任何关于参数splatting的语法.它在Swift-Evolution中被不止一次讨论过(例如在这个帖子中),但还没有解决方案.
这个问题的通常解决方案是寻找一个伴随函数,它将已经捆绑的可变参数作为单个参数.通常,伴侣会v
添加到函数名称中.例子:
printf
(variadic)和vprintf
(取a va_list
,C相当于Array<CVarArg>
)NSLog
(可变的)和NSLogv
(需要va_list
)-[NSString initWithFormat:]
(可变的)和-[NSString WithFormat:arguments:]
(需要va_list
)所以你可能会去找一个os_logv
.可悲的是,你找不到一个.没有记录的伴随os_log
物需要预先捆绑的参数.
此时您有两种选择:
放弃os_log
在你自己的可变包装器中包装,因为没有好的方法可以做到,或者
以Kamran的建议(在他对你的问题的评论中)并使用%@
而不是%f
.但请注意,您%@
的消息字符串中只能有一个(而不是其他格式说明符),因为您只传递一个参数os_log
.输出如下所示:
2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
Run Code Online (Sandbox Code Playgroud)您也可以在https://bugreport.apple.com上提交增强请求雷达,询问os_logv
功能,但您不应期望它会在短时间内实施.
就是这样了.做这两件事中的一件,也许提出雷达,继续你的生活.认真.别在这里读.这一行之后没什么好处的.
好的,你一直在读.让我们一起来看看os_log
.事实证明,Swift os_log
函数的实现是公共Swift源代码的一部分:
Run Code Online (Sandbox Code Playgroud)@_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>
a 转换为a va_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)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
它有效.测试代码:
func testWrapper() {
logDefault("WTF: %f", 1.2345)
logDefault("WTF: %@", 1.2345)
logDefaultHack("Hack: %f", 1.2345)
}
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
我会推荐这个解决方案吗?不,地狱没有.内部结构os_log
是一个实现细节,可能会在未来版本的Swift中发生变化.所以不要像这样依赖他们.但无论如何,看看封面是有趣的.
最后一件事.为什么编译器没有抱怨转换Array<CVarArg>
为CVarArg
?为什么Kamran的建议(使用%@
)有效?
事实证明,这些问题具有相同的答案:因为它Array
与Objective-C对象"可桥接".特别:
Foundation(在Apple平台上)Array
符合_ObjectiveCBridgeable
协议.它Array
通过返回一个来实现对Objective-C的桥接NSArray
.
该withVaList
函数要求每个CVarArg
人将自己转换为它_cVarArgEncoding
.
默认实现的_cVarArgEncoding
,因为这符合两国类型_ObjectiveCBridgeable
和CVarArg
返回桥接Objective-C的对象.
的一致性Array
,以CVarArg
表示编译器不会抱怨(静默)转换的Array<CVarArg>
一个CVarArg
,并将其粘贴到另一个Array<CVarArg>
.
这种静默转换可能通常是一个错误(就像你的情况一样),所以编译器警告它是合理的,并允许你用显式转换(例如args as CVarArg
)来使警告静音.如果需要,您可以在https://bugs.swift.org上提交错误报告.
归档时间: |
|
查看次数: |
2322 次 |
最近记录: |