考虑以下示例:
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
我已经检查了所有可以想到的内容,并且无法弄清这里出了什么问题。浏览文档不会产生任何帮助。
感谢您的帮助!
答案 0 :(得分:49)
编译器通过将每个参数强制转换为声明的可变参数类型,将它们包装到该类型的Array
中,然后将该数组传递给可变参数函数来实现可变参数。对于testWrapper
,声明的可变参数类型为CVarArg
,因此,当testWrapper
调用logDefault
时,情况就是这样:testWrapper
强制转换{ {1}}到1.2345
,创建一个CVarArg
,并将其作为Array<CVarArg>
传递给logDefault
。
然后args
调用logDefault
,并将os_log
作为参数传递给它。 这是您代码中的错误。该错误非常微妙。问题在于Array<CVarArg>
不接受os_log
参数; Array<CVarArg>
本身对os_log
来说是可变的。因此,Swift将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)
将此新的logDefault
传递给Array<CVarArg>
。因此,您要os_log
使用os_log
来格式化其第一个元素Array<CVarArg>
(是%f
的某种形式),而这是胡说八道,而您碰巧得到了0.000000
作为输出。 (我说“有点”是因为这里有些微妙之处,我稍后会解释。)
因此,logDefault
将其传入的Array<CVarArg>
作为许多可变参数之一传递给os_log
,但是您实际上希望logDefault
要做的是传递传入的{ {1}}作为Array<CVarArg>
的可变参数的整个集合,而无需重新包装。在其他语言中有时也称为“参数展开”。
对您来说很不幸,Swift尚无任何用于参数展开的语法。在Swift-Evolution(in this thread, for example)中已经讨论了多次,但是目前还没有解决方案。
此问题的通常解决方案是寻找一个伴随函数,该函数将已经捆绑的可变参数作为单个参数。通常,同伴在函数名称中添加了os_log
。例子:
v
(可变)和printf
(取vprintf
,C等于va_list
)Array<CVarArg>
(可变)和NSLog
(取NSLogv
)va_list
(可变)和-[NSString initWithFormat:]
(取-[NSString WithFormat:arguments:]
)因此,您可能会去寻找va_list
。可悲的是,您找不到一个。 os_logv
没有记录为预捆绑参数的同伴。
此时您有两个选择:
放弃os_log
用自己的可变包装器包装,因为根本没有很好的方法,或者
接受Kamran的建议(在对您的问题的评论中),并使用os_log
代替%@
。但是请注意,您的消息字符串中只能有一个%f
(并且不能有其他格式说明符),因为您只是将单个参数传递给%@
。输出看起来像这样:
os_log
您也可以在https://bugreport.apple.com上申请请求2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
功能的增强请求雷达,但是您不希望它很快实现。
就这样。做这两件事之一,也许要提起雷达,然后继续生活。说真的在这里停止阅读。这行之后没什么好处。
好的,你一直在读书。让我们来看看os_logv
的背后。事实证明,Swift os_log
函数的实现是public Swift source code的一部分:
os_log
因此事实证明,是一个@_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包装器使用_swift_os_log
(已记录)将withVaList
转换为Array<CVarArg>
并将其传递到va_list
,后者本身也是{{3 }}。我不会在这里引用它的代码,因为它很长而且我们实际上不需要查看它。
无论如何,即使没有记录,我们实际上也可以调用_swift_os_log
。我们基本上可以复制_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
的内部是实现细节,在Swift的未来版本中可能会更改。所以不要这样依赖他们。但是无论如何,在幕后找东西还是很有趣的。
最后一件事。编译器为何不抱怨将os_log
转换为Array<CVarArg>
?为何Kamran的建议(使用CVarArg
)有效?
事实证明,这些问题的答案是相同的:这是因为%@
可“桥接”到Objective-C对象。具体来说:
public Swift source code通过返回Array
实现Array
与Objective-C的桥接。
Foundation (on Apple platforms) makes Array
conform to the _ObjectiveCBridgeable
protocol.。
NSArray
函数要求每个withVaList
到Foundation also makes Array
conform to the CVarArg
protocol。
convert itself to its _cVarArgEncoding
,对于同时符合CVarArg
和_ObjectiveCBridgeable
的类型,返回桥接的Objective-C对象。
CVarArg
与Array
的一致性意味着编译器不会抱怨(默默地)将CVarArg
转换为Array<CVarArg>
并将其粘贴到另一个CVarArg
。
这种静默转换通常可能是一个错误(如您的情况),因此编译器对其进行警告是合理的,并允许您使用显式强制转换(例如{{1} }。您可以根据需要在The default implementation of _cVarArgEncoding
提交错误报告。
答案 1 :(得分:0)
正如我在上面对Rob Mayoff的回答的评论中所提到的,对于select
groupe,
id,
case
when bool_and(status = 'up') then 'up'
when bool_or(status = 'notdefined') then 'notdefined'
else 'notpecified'
end as status
from tab
group by 1, 2
order by 1, 2
遇到同样问题的任何人,下面是我围绕它编写的包装器类:
os_signpost()