考虑以下示例:

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时,情况就是这样:testWrapper1.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 参数的伴侣。

    此时,您有两个选择:
  • 放弃用自己的可变参数包装器包装%@的方法,因为根本没有做到这一点的好方法,或者说
  • 接受Kamran的建议(在对您的问题的评论中),并使用%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对象。具体来说:
  • Foundation (on Apple platforms) makes _ObjectiveCBridgeable conform to the Array protocol.通过返回NSArray来实现Array与Objective-C的桥接。
  • Foundation also makes CVarArg conform to the withVaList protocol
  • CVarArg函数向convert itself to its _cVarArgEncoding 询问每个_cVarArgEncoding
  • 对于同时符合_ObjectiveCBridgeableCVarArg的类型,
  • The default implementation of Array 返回桥接的Objective-C对象。
  • CVarArgArray<CVarArg>的一致性意味着编译器不会提示(默默地)将CVarArg转换为Array<CVarArg>并将其粘贴到另一个args as CVarArg中。

  • 这种静默转换通常可能是一个错误(就像您的情况一样),因此编译器对其进行警告是合理的,并允许您使用显式强制转换(例如ojit_code)使警告静默。如果需要,可以在https://bugs.swift.org上提交错误报告。

    关于ios - 为什么包装os_log()导致double不能正确记录?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50937765/

    10-10 21:03