为什么包装os_log()导致double不能正确记录?

时间:2018-06-19 22:28:29

标签: ios swift logging

考虑以下示例:

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

我已经检查了所有可以想到的内容,并且无法弄清这里出了什么问题。浏览文档不会产生任何帮助。

感谢您的帮助!

2 个答案:

答案 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对象。具体来说:

这种静默转换通常可能是一个错误(如您的情况),因此编译器对其进行警告是合理的,并允许您使用显式强制转换(例如{{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()