You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS封装os_log使用%s格式化符时崩溃问题求助

问题:封装iOS os_log日志工具时调用崩溃的原因分析

场景复现

我正在为iOS应用封装os_log日志工具,封装代码如下:

import Foundation
import OSLog

enum LogCategory: String, CaseIterable {
    case viewCycle
    case tracking
    case api
}

struct Log {
    
    private static let logs = {
        return LogCategory.allCases
            .reduce(into: [LogCategory: OSLog]()) { dict, category in
                dict[category] = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "BIMB", category: category.rawValue)
            }
    }()

    static func debug(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .debug, args)
    }

    static func info(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .info, args)
    }
    
    static func notice(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .default, args)
    }
    
    static func warning(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .default, args)
    }
    
    static func error(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .error, args)
    }
    
    static func critical(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .fault, args)
    }
    
    private static func logImpl(category: LogCategory, message: StaticString, type: OSLogType, _ args: CVarArg...) {
        guard let log = logs[category] else {
            return
        }
        os_log(message, log: log, type: type, args)
    }
}

调用封装的日志方法时应用崩溃:

Log.debug(category: .tracking, 
          message: "Device ID: %s.", 
          UIDevice.current.identifierForVendor?.uuidString ?? "unknown")

崩溃日志:

2023-12-13 12:33:35.173798+0700 bimb-authenticate-ios[62740:928633]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Swift.__SwiftDeferredNSArray UTF8String]: unrecognized selector sent to instance 0x600000dcbbc0'

但直接调用os_log却能正常运行:

os_log("Device ID: %s.",
       log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "tracking"),
       type: .debug,
       UIDevice.current.identifierForVendor?.uuidString ?? "unknown")

崩溃原因

问题出在可变参数的传递方式上:

  • 当你在debug等上层方法中传入args给logImpl时,Swift会把可变参数打包成一个[CVarArg]数组,作为单个参数传递给logImpl的可变参数列表。
  • 而os_log的可变参数期望接收多个独立的CVarArg值,不是一个数组。此时%s格式符会尝试把这个数组对象当作C字符串处理,调用它的UTF8String方法,但Swift数组并没有这个方法,因此触发崩溃。

解决方案

修改logImpl方法,不再使用可变参数,而是直接接收[CVarArg]数组,然后通过withVaList将数组转换为C语言的可变参数列表,再调用os_logv(os_log的底层可变参数版本):

import Foundation
import OSLog

enum LogCategory: String, CaseIterable {
    case viewCycle
    case tracking
    case api
}

struct Log {
    
    private static let logs = {
        return LogCategory.allCases
            .reduce(into: [LogCategory: OSLog]()) { dict, category in
                dict[category] = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "BIMB", category: category.rawValue)
            }
    }()

    static func debug(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .debug, args: args)
    }

    static func info(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .info, args: args)
    }
    
    static func notice(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .default, args: args)
    }
    
    static func warning(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .default, args: args)
    }
    
    static func error(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .error, args: args)
    }
    
    static func critical(category: LogCategory, message: StaticString, _ args: CVarArg...) {
        logImpl(category: category, message: message, type: .fault, args: args)
    }
    
    private static func logImpl(category: LogCategory, message: StaticString, type: OSLogType, args: [CVarArg]) {
        guard let log = logs[category] else {
            return
        }
        withVaList(args) { vaList in
            os_logv(message, log: log, type: type, vaList)
        }
    }
}

这样修改后,args数组会被正确转换为os_logv需要的可变参数列表,格式符和参数类型就能正确匹配,不会再出现崩溃。

内容的提问来源于stack exchange,提问作者Bawenang Rukmoko Pardian Putra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 10:11:10