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
相关产品推荐
相关产品推荐

