在 Swift 4 中为 os_log 传递可变参数

Passing variadic args in Swift 4 for os_log

我正尝试在 Swift 4 / iOS 11 中为 os_log 编写一个方便的包装器,但我已经 运行 通过可变参数。

基本上,我想编写一个如下所示的函数。

static let logger = OSLog(subsystem: "com.example.foo", category: "foobar")
func logError(_ message: StaticString, _ args: Any...) {
    os_log(message, log: logger, type: .error, args)
}

不幸的是,我似乎无法弄清楚传递参数的神奇语法,并且在 CVarArg 讨论的泥潭中有点迷失了方向。

(...这让我想念 Python 的拼写语法)

对此进行了更多研究。原来 os_log 实际上是一个 C 宏。这导致了它如何映射到 Swifts 可变参数的各种问题。

但是,该宏还会捕获其他调试信息,无论如何都可能不安全。

我还没有找到解决方案,所以做了这个愚蠢的 hack:

switch args.count {
case 0:
    os_log(message, log: log!, type: type)
case 1:
    os_log(message, log: log!, type: type, args[0])
case 2:
    os_log(message, log: log!, type: type, args[0], args[1])
case 3:
    os_log(message, log: log!, type: type, args[0], args[1], args[2])
default:
    os_log(message, log: log!, type: type, args)
}

这是我用来包装的 os_log:

import Foundation
import os.log

protocol LogServicing: class {

    func debug(_ message: StaticString, _ args: CVarArg...)
    func info(_ message: StaticString, _ args: CVarArg...)
    func error(_ message: StaticString, _ args: CVarArg...)

}

enum LogType {
    case debug
    case info
    case error
    case fault
}

class LogService: LogServicing {

    private var osLog: OSLog?
    let subsystem: String
    let category: String

    init(subsystem: String = Bundle.main.bundleIdentifier ?? "", category: String = "") {
        if #available(iOS 10.0, *) {
            let osLog = OSLog(subsystem: subsystem, category: category)
            self.osLog = osLog
        }
        self.subsystem = subsystem
        self.category = category
    }

    func log(type: LogType, message: StaticString) {
        log(type: type, message: message, "")
    }

    func log(type: LogType, message: StaticString, _ args: CVarArg...) {
        if #available(iOS 10.0, *) {
            guard let osLog = osLog else { return }
            let logType: OSLogType
            switch type {
            case .debug:
                logType = .debug
            case .error:
                logType = .error
            case .fault:
                logType = .fault
            case .info:
                logType = .info
            }
            os_log(message, log: osLog, type: logType, args)
            print(message, args)
        } else {
            NSLog(message.description, args)
        }
    }

    func debug(_ message: StaticString, _ args: CVarArg...) {
        log(type: .debug, message: message, args)
    }

    func info(_ message: StaticString, _ args: CVarArg...) {
        log(type: .info, message: message, args)
    }

    func error(_ message: StaticString, _ args: CVarArg...) {
        log(type: .error, message: message, args)
    }
}

我是这样创建的:

self.logService = LogService(subsystem: "com.softbolt.app", category: "network")

并像这样使用它:

self.logService.info("HttpResponse %{public}@", url)

如果您想了解更多关于 os_log 以及私有和 public 日志记录的好处,请查看此 link:

https://www.testdevlab.com/blog/2018/04/how-to-create-categorize-and-filter-ios-logs/

您的想法包含几个问题:

  1. A​​pple 不鼓励在另一个函数中包装 os_log,这样做会导致失去统一日志系统的一些很好的功能,比如在自动记录。

  2. 一旦 args 被传递给你自己的函数,类型从 cvargs 传递给 [String] 并且理论上不可能 re-build args 列表,你可以在第一个答案中找到一个惊人的解释: