|
@@ -0,0 +1,136 @@
|
|
|
+//
|
|
|
+// CrashReporter.swift
|
|
|
+// AIEmoji
|
|
|
+//
|
|
|
+// Created by 100Years on 2025/4/17.
|
|
|
+//
|
|
|
+
|
|
|
+import UIKit
|
|
|
+
|
|
|
+class CrashReporter {
|
|
|
+
|
|
|
+ // 单例模式
|
|
|
+ static let shared = CrashReporter()
|
|
|
+ private init() {}
|
|
|
+
|
|
|
+ // 最大保存崩溃记录数
|
|
|
+ private let maxCrashReports = 1000
|
|
|
+
|
|
|
+ // 初始化崩溃捕获
|
|
|
+ func setup() {
|
|
|
+ // 保存之前未上传的崩溃报告(如果有)
|
|
|
+ savePendingCrashReports()
|
|
|
+
|
|
|
+ // 设置异常捕获
|
|
|
+ NSSetUncaughtExceptionHandler { exception in
|
|
|
+ CrashReporter.shared.saveCrashReport(exception: exception)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存崩溃报告
|
|
|
+ private func saveCrashReport(exception: NSException) {
|
|
|
+ let crashInfo: [String: Any] = [
|
|
|
+ "name": exception.name.rawValue,
|
|
|
+ "reason": exception.reason ?? "",
|
|
|
+ "callStackSymbols": exception.callStackSymbols,
|
|
|
+ "date": Date().timeIntervalSince1970,
|
|
|
+ "appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "",
|
|
|
+ "osVersion": UIDevice.current.systemVersion
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 获取现有崩溃报告
|
|
|
+ var existingReports = getCrashReports()
|
|
|
+
|
|
|
+ // 添加新报告
|
|
|
+ existingReports.append(crashInfo)
|
|
|
+
|
|
|
+ // 限制最大数量(保留最新的)
|
|
|
+ if existingReports.count > maxCrashReports {
|
|
|
+ existingReports.removeFirst(existingReports.count - maxCrashReports)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到UserDefaults
|
|
|
+ saveReportsToUserDefaults(reports: existingReports)
|
|
|
+
|
|
|
+ // 同时写入文件作为备份
|
|
|
+ saveReportsToFile(reports: existingReports)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有崩溃报告(不自动删除)
|
|
|
+ func getCrashReports() -> [[String: Any]] {
|
|
|
+ // 首先尝试从UserDefaults读取
|
|
|
+ if let reports = UserDefaults.standard.array(forKey: "CrashReports") as? [[String: Any]] {
|
|
|
+ return reports
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果UserDefaults中没有,尝试从文件恢复
|
|
|
+ if let fileReports = loadReportsFromFile() {
|
|
|
+ saveReportsToUserDefaults(reports: fileReports)
|
|
|
+ return fileReports
|
|
|
+ }
|
|
|
+
|
|
|
+ return []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动删除崩溃报告
|
|
|
+ func deleteCrashReports() {
|
|
|
+ UserDefaults.standard.removeObject(forKey: "CrashReports")
|
|
|
+
|
|
|
+ // 同时删除文件备份
|
|
|
+ if let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
|
|
|
+ let path = (documents as NSString).appendingPathComponent("crash_reports.plist")
|
|
|
+ try? FileManager.default.removeItem(atPath: path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 标记崩溃报告为已处理(可选)
|
|
|
+ func markReportsAsProcessed(_ indices: [Int]) {
|
|
|
+ var reports = getCrashReports()
|
|
|
+ for index in indices where index < reports.count {
|
|
|
+ reports[index]["processed"] = true
|
|
|
+ }
|
|
|
+ saveReportsToUserDefaults(reports: reports)
|
|
|
+ }
|
|
|
+
|
|
|
+ func printCrashReports(){
|
|
|
+ let crashReports = CrashReporter.shared.getCrashReports()
|
|
|
+ for report in crashReports {
|
|
|
+ print("崩溃时间: \(Date(timeIntervalSince1970: report["date"] as? TimeInterval ?? 0))")
|
|
|
+ print("崩溃原因: \(report["reason"] as? String ?? "")")
|
|
|
+ print("堆栈跟踪: \(report["callStackSymbols"] as? [String] ?? [])")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - 私有方法
|
|
|
+
|
|
|
+ private func saveReportsToUserDefaults(reports: [[String: Any]]) {
|
|
|
+ UserDefaults.standard.set(reports, forKey: "CrashReports")
|
|
|
+ UserDefaults.standard.synchronize()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func saveReportsToFile(reports: [[String: Any]]) {
|
|
|
+ guard let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let path = (documents as NSString).appendingPathComponent("crash_reports.plist")
|
|
|
+ (reports as NSArray).write(toFile: path, atomically: true)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func loadReportsFromFile() -> [[String: Any]]? {
|
|
|
+ guard let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ let path = (documents as NSString).appendingPathComponent("crash_reports.plist")
|
|
|
+ return NSArray(contentsOfFile: path) as? [[String: Any]]
|
|
|
+ }
|
|
|
+
|
|
|
+ private func savePendingCrashReports() {
|
|
|
+ // 如果有文件备份但UserDefaults中没有,恢复数据
|
|
|
+ if UserDefaults.standard.array(forKey: "CrashReports") == nil,
|
|
|
+ let fileReports = loadReportsFromFile() {
|
|
|
+ saveReportsToUserDefaults(reports: fileReports)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|