Parcourir la source

增加闪退记录工具

100Years il y a 5 jours
Parent
commit
8b23ffa70d

+ 4 - 0
AIEmoji.xcodeproj/project.pbxproj

@@ -164,6 +164,7 @@
 		A8BA76722DA65A95000B6707 /* TSAIUploadPhotoBaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA76712DA65A94000B6707 /* TSAIUploadPhotoBaseVC.swift */; };
 		A8BA76752DA67E66000B6707 /* TSAIListHistoryBaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA76742DA67E65000B6707 /* TSAIListHistoryBaseVC.swift */; };
 		A8BA76772DA68619000B6707 /* TSAIListHistoryBaseVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA76762DA68617000B6707 /* TSAIListHistoryBaseVM.swift */; };
+		A8D638352DB10BAC00A96C0E /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638342DB10BAB00A96C0E /* CrashReporter.swift */; };
 		A8EEADD42D3E6C660032C5A0 /* Flower💐.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD32D3E6C610032C5A0 /* Flower💐.json */; };
 		A8EEADD62D3E6CD80032C5A0 /* Fish🐠.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */; };
 		A8EEADD82D3E74D20032C5A0 /* Pink🩷.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */; };
@@ -414,6 +415,7 @@
 		A8BA76712DA65A94000B6707 /* TSAIUploadPhotoBaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIUploadPhotoBaseVC.swift; sourceTree = "<group>"; };
 		A8BA76742DA67E65000B6707 /* TSAIListHistoryBaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseVC.swift; sourceTree = "<group>"; };
 		A8BA76762DA68617000B6707 /* TSAIListHistoryBaseVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseVM.swift; sourceTree = "<group>"; };
+		A8D638342DB10BAB00A96C0E /* CrashReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = "<group>"; };
 		A8EEADD32D3E6C610032C5A0 /* Flower💐.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Flower💐.json"; sourceTree = "<group>"; };
 		A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Fish🐠.json"; sourceTree = "<group>"; };
 		A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Pink🩷.json"; sourceTree = "<group>"; };
@@ -1516,6 +1518,7 @@
 		A8F774CE2D38EA8C00AA6E93 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
+				A8D638342DB10BAB00A96C0E /* CrashReporter.swift */,
 				A8BA764E2DA50B52000B6707 /* CpuMapManager.swift */,
 				A85E479E2D6859F80018D62D /* TSRandomTextPicker.swift */,
 				A8F774C82D38EA8C00AA6E93 /* TSCommonTool */,
@@ -2067,6 +2070,7 @@
 				A8BA766C2DA657E8000B6707 /* TSAIListPhotoGeneratorBaseVC.swift in Sources */,
 				A80EDD5C2D6C3F82003CD332 /* MarkdownList.swift in Sources */,
 				A80EDD5D2D6C3F82003CD332 /* MarkdownCodeEscaping.swift in Sources */,
+				A8D638352DB10BAC00A96C0E /* CrashReporter.swift in Sources */,
 				A80EDD5E2D6C3F82003CD332 /* MarkdownLinkElement.swift in Sources */,
 				A80EDD5F2D6C3F82003CD332 /* MarkdownHeader+UIKit.swift in Sources */,
 				A80EDE062D6F3491003CD332 /* TSPTPBrowseVC.swift in Sources */,

+ 3 - 0
AIEmoji/AppDelegate.swift

@@ -79,6 +79,9 @@ extension AppDelegate {
 //        let userDefaults = UserDefaults.standard
 //        let lastGreetingDateString = userDefaults.string(forKey: "kEveryDayPopPurchase")
 //        userDefaults.set(String(Int(lastGreetingDateString!)!+1), forKey: "kEveryDayPopPurchase")//测试用的
+        
+        CrashReporter.shared.setup()
+        CrashReporter.shared.printCrashReports()
     }
     
     func beginBackgroundTask() {

BIN
AIEmoji/Assets.xcassets/PTP/ptp_example_image0.imageset/ptp_example_image0@2x.png


BIN
AIEmoji/Assets.xcassets/PTP/ptp_example_image0.imageset/ptp_example_image0@3x.png


+ 30 - 0
AIEmoji/Business/VIewTool/TSGeneratorloadingView.swift

@@ -32,11 +32,41 @@ class TSGeneratorloadingView: TSBaseView {
         return animatedImageView
     }()
     
+    
+    
+    lazy var timeLabelBgView: UIView = {
+        let view = UIView()
+        view.clipsToBounds = true
+        view.addSubview(timeLabel)
+        timeLabel.snp.makeConstraints { make in
+            make.top.equalTo(0)
+            make.leading.equalTo(16)
+            make.trailing.equalTo(-16)
+            make.height.equalTo(27)
+            make.bottom.equalTo(-17)
+        }
+        return view
+    }()
+
     lazy var timeLabel: UILabel = {
         let textLabel = UILabel.createLabel(font: .font(size: 18,weight: .semibold),textColor: .white,textAlignment: .center)
         return textLabel
     }()
     
+    lazy var textLabelBgView: UIView = {
+        let view = UIView()
+        view.clipsToBounds = true
+        view.addSubview(textLabel)
+        textLabel.snp.makeConstraints { make in
+            make.top.equalTo(0)
+            make.leading.equalTo(16)
+            make.trailing.equalTo(-16)
+            make.height.equalTo(27)
+            make.bottom.equalTo(-8)
+        }
+        return view
+    }()
+    
     lazy var textLabel: UILabel = {
         let textLabel = UILabel.createLabel(font: .font(size: 18),textColor: .white,textAlignment: .center)
         return textLabel

+ 136 - 0
AIEmoji/Common/Tool/CrashReporter.swift

@@ -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)
+        }
+    }
+}