为什么包装os_log()导致double不能正确记录? [英] Why does wrapping os_log() cause doubles to not be logged correctly?

查看:86
本文介绍了为什么包装os_log()导致double不能正确记录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下示例:

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()

If I create a new instance of OSLogWrapper and call testWrapper()

let logger = OSLogWrapper()
logger.testWrapper()

我在Xcode控制台中得到以下输出:

I get the following output in the Xcode console:

2018-06-19 18:21:08.327979-0400 WrapperWTF[50240:548958] WTF: 0.000000

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

I've checked everything I can think of and I can't make heads or tails of what's going wrong here. Looking through the documentation isn't yielding anything helpful.

推荐答案

编译器通过将每个参数强制转换为声明的可变参数类型,并将其包装到该类型的Array中,并将该数组传递给可变参数来实现可变参数功能.对于testWrapper,声明的可变参数类型为CVarArg,因此,当testWrapper调用logDefault时,情况就是这样:testWrapper1.2345强制转换为CVarArg,创建一个Array<CVarArg>,并将其作为args传递给logDefault.

The compiler implements variadic arguments by casting each argument to the declared variadic type, packaging them into an Array of that type, and passing that array to the variadic function. In the case of testWrapper, the declared variadic type is CVarArg, so when testWrapper calls logDefault, this is what happens under the covers: testWrapper casts 1.2345 to a CVarArg, creates an Array<CVarArg>, and passes it to logDefault as args.

然后logDefault调用os_log,并将Array<CVarArg>作为参数传递给它. 这是您代码中的错误.该错误非常微妙.问题在于os_log不接受Array<CVarArg>自变量. os_log本身对CVarArg而言是可变的.因此,Swift将args(一个Array<CVarArg>)强制转换为CVarArg,并将将CVarArg强制转换为另一个 Array<CVarArg>.结构如下:

Then logDefault calls os_log, passing it that Array<CVarArg> as an argument. This is the bug in your code. The bug is quite subtle. The problem is that os_log doesn't take an Array<CVarArg> argument; os_log is itself variadic over CVarArg. So Swift casts args (an Array<CVarArg>) to CVarArg, and sticks that casted CVarArg into another Array<CVarArg>. The structure looks like this:

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.因此,您要使用%f来格式化os_log的第一个元素(是Array<CVarArg>的某种),这是毫无意义的,而您恰巧得到了0.000000作为输出. (我说有点"是因为这里有些微妙之处,我稍后会解释.)

Then logDefault passes this new Array<CVarArg> to os_log. So you're asking os_log to format its first element, which is (sort of) an Array<CVarArg>, using %f, which is nonsense, and you happen to get 0.000000 as output. (I say "sort of" because there are some subtleties here which I explain later.)

因此,logDefault将其传入的Array<CVarArg>作为可能的各种可变参数之一传递给os_log,但是您真正想要的logDefault要做的是将该传入的Array<CVarArg>作为整个可变参数传递参数设置为os_log,而无需重新包装.在其他语言中,有时也称为参数展开".

So, logDefault passes its incoming Array<CVarArg> as one of potentially many variadic parameters to os_log, but what you actually want logDefault to do is pass on that incoming Array<CVarArg> as the entire set of variadic parameters to os_log, without re-wrapping it. This is sometimes called "argument splatting" in other languages.

对您来说很不幸,Swift尚无任何用于参数展开的语法.在Swift-Evolution(例如),但目前还没有解决方案.

Sadly for you, Swift doesn't yet have any syntax for argument splatting. It's been discussed more than once in Swift-Evolution (in this thread, for example), but there's not yet a solution on the horizon.

此问题的通常解决方案是寻找一个伴随函数,该函数将已经捆绑的可变参数作为单个参数.通常,同伴在函数名称中添加了v.例子:

The usual solution to this problem is to look for a companion function that takes the already-bundled-up variadic arguments as a single argument. Often the companion has a v added to the function name. Examples:

  • printf(可变)和vprintf(采用va_list,C等效于Array<CVarArg>)
  • NSLog(可变)和NSLogv(采用va_list)
  • -[NSString initWithFormat:](可变)和-[NSString WithFormat:arguments:](采用va_list)
  • printf (variadic) and vprintf (takes a va_list, C's equivalent of Array<CVarArg>)
  • NSLog (variadic) and NSLogv (takes a va_list)
  • -[NSString initWithFormat:] (variadic) and -[NSString WithFormat:arguments:] (takes a va_list)

所以您可能会寻找os_logv.可悲的是,您找不到一个. os_log没有附带预捆绑参数的伴侣.

So you might go looking for an os_logv. Sadly, you won't find one. There is no documented companion to os_log that takes pre-bundled arguments.

这时您有两个选择:

  • 放弃使用自己的可变参数包装器包装os_log,因为根本没有什么好办法,或者

  • Give up on wrapping os_log in your own variadic wrapper, because there is simply no good way to do it, or

接受Kamran的建议(在对您的问题的评论中),并使用%@而不是%f.但是请注意,您的消息字符串中只能有一个%@(没有其他格式说明符),因为您只将一个参数传递给os_log.输出看起来像这样:

Take Kamran's advice (in his comment on your question) and use %@ instead of %f. But note that you can only have a single %@ (and no other format specifiers) in your message string, because you're only passing a single argument to os_log. The output looks like this:

2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
    "1.2345"
)

您还可以通过 https://bugreport.apple.com 提交增强请求雷达,要求提供os_logv函数,但您不应指望它会很快实现.

You could also file an enhancement request radar at https://bugreport.apple.com asking for an os_logv function, but you shouldn't expect it to be implemented any time soon.

就这样.做这两个事情之一,也许要提起雷达,然后继续生活.严重地.在这里停止阅读.这行之后没什么好处.

So that's it. Do one of those two things, maybe file a radar, and move on with your life. Seriously. Stop reading here. There's nothing good after this line.

好的,你一直在读书.让我们来看看os_log的含义.事实证明,Swift os_log函数的实现是 Swift公开源代码:

Okay, you kept reading. Let's peek under the hood of os_log. It turns out the implementation of the Swift os_log function is part of the 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,而这本身也是

So it turns out there is a version of os_log, called _swift_os_log, that takes pre-bundled arguments. The Swift wrapper uses withVaList (which is documented) to convert the Array<CVarArg> to a va_list and passes that on to _swift_os_log, which is itself also part of the public Swift source code. I won't bother quoting its code here because it's long and we don't actually need to look at it.

无论如何,即使没有记录,我们实际上也可以调用_swift_os_log.我们基本上可以复制os_log的源代码并将其转换为您的logDefault函数:

Anyway, even though it's not documented, we can actually call _swift_os_log. We can basically copy the source code of os_log and turn it into your logDefault function:

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

我会推荐这种解决方案吗?不.地狱不. os_log的内部是实现细节,并且在以后的Swift版本中可能会发生变化.因此,不要像这样依靠他们.但是无论如何,在幕后找东西还是很有趣的.

Would I recommend this solution? No. Hell no. The internals of os_log are an implementation detail and likely to change in future versions of Swift. So don't rely on them like this. But it's interesting to look under the covers anyway.

最后一件事.为何编译器不抱怨将Array<CVarArg>转换为CVarArg?为什么Kamran的建议(使用%@)起作用?

One last thing. Why doesn't the compiler complain about converting Array<CVarArg> to CVarArg? And why does Kamran's suggestion (of using %@) work?

事实证明,这些问题的答案是相同的:这是因为Array可桥接"到Objective-C对象.具体来说:

It turns out these questions have the same answer: it's because Array is "bridgeable" to an Objective-C object. Specifically:

  • Foundation (on Apple platforms) makes Array conform to the _ObjectiveCBridgeable protocol. It implements bridging of Array to Objective-C by returning an NSArray.

基础也使Array符合CVarArg协议.

withVaList函数要求每个CVarArg将其自身转换为其_cVarArgEncoding .

The withVaList function asks each CVarArg to convert itself to its _cVarArgEncoding.

默认实现对于同时符合_ObjectiveCBridgeableCVarArg的类型的_cVarArgEncoding ,返回桥接的Objective-C对象.

The default implementation of _cVarArgEncoding, for a type that conforms to both _ObjectiveCBridgeable and CVarArg, returns the bridging Objective-C object.

ArrayCVarArg的一致性意味着编译器不会抱怨(默默地)将Array<CVarArg>转换为CVarArg并将其粘贴到另一个Array<CVarArg>中.

The conformance of Array to CVarArg means the compiler won't complain about (silently) converting an Array<CVarArg> to a CVarArg and sticking it into another Array<CVarArg>.

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

This silent conversion is probably often an error (as it was in your case), so it would be reasonable for the compiler to warn about it, and allow you to silence the warning with an explicit cast (e.g. args as CVarArg). You could file a bug report at https://bugs.swift.org if you want.

这篇关于为什么包装os_log()导致double不能正确记录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆