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

如何修复EXC_BAD_ACCESS KERN_INVALID_ADDRESS崩溃(Firestore相关)

Firebase Firestore随机崩溃问题排查与解决

问题现象

  • 应用启动后几秒内随机触发崩溃,无法稳定复现
  • 仅在查询用户所有帖子的GetDataGetItemsInSpecificHome方法中出现崩溃
  • 崩溃发生在调用FIRDocumentSnapshot的data属性时
  • 其他结构类似的数据查询方法无此问题,且已确认被查询的数据正常
  • 方法外层的Objective-C @try-catch无法捕获该崩溃

崩溃日志分析

从崩溃栈可以看到,崩溃发生在Firebase Firestore的内部数据转换逻辑中:

Crashed: com.apple.main-thread 
0  FirebaseFirestore              0x905a4 -[FSTUserDataWriter convertedValue:] + 268 
1  FirebaseFirestore              0x906cc -[FSTUserDataWriter convertedObject:] + 120 
2  FirebaseFirestore              0x90610 -[FSTUserDataWriter convertedValue:] + 376 
3  FirebaseFirestore              0x5865c -[FIRDocumentSnapshot dataWithServerTimestampBehavior:] + 200 
4  FirebaseFirestore              0x58b14 -[FIRQueryDocumentSnapshot dataWithServerTimestampBehavior:] + 44 
5  FirebaseFirestore              0x58a70 -[FIRQueryDocumentSnapshot data] + 44 

这说明崩溃是Firestore在将服务器返回的数据转换为Objective-C对象时触发的,属于底层C++层面的异常/错误,因此Objective-C的@try-catch无法捕获(仅能处理ObjC异常)。

代码问题点

当前代码存在以下风险:

  1. 未检查FIRDocumentSnapshot是否存在:直接遍历snapshot.documents但未判断doc.exists,可能处理无效文档
  2. 重复调用doc.data属性:每次调用都会触发Firestore的数据解析逻辑,增加崩溃概率
  3. 未处理数据解析时的异常:直接访问doc.data,没有做防护

解决方案

1. 基础防护:检查文档有效性+缓存解析结果

修改代码,先判断文档是否存在,并且只调用一次doc.data,避免重复解析:

-(void)GetDataGetItemsInSpecificHome:(NSString *)homeID collection:(NSString *)collection keyArray:(NSArray *)keyArray currentViewController:(UIViewController *)currentViewController crashlyticsString:(NSString *)crashlyticsString completionHandler:(void (^)(BOOL finished, NSMutableDictionary *returningDataDict))finishBlock {

    [[FIRCrashlytics crashlytics] logWithFormat:@"GetDataGetItemsInSpecificHome %@ Processing(1)", collection];           

    __block NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];           

    FIRFirestore *defaultFirestore = [FIRFirestore firestore];           

    [[[defaultFirestore collectionWithPath:collection] queryWhereField:@"ItemHomeID" isEqualTo:homeID] getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {    

        if (error) {
            [[FIRCrashlytics crashlytics] recordError:error];
            finishBlock(YES, [NSMutableDictionary dictionary]);
            return;
        }

        [[FIRCrashlytics crashlytics] logWithFormat:@"GetDataGetItemsInSpecificHome %@ Processing(2)", collection];                  

        for (FIRQueryDocumentSnapshot *doc in snapshot.documents) {
            // 先检查文档是否存在
            if (!doc.exists) {
                [[FIRCrashlytics crashlytics] logWithFormat:@"GetDataGetItemsInSpecificHome: Skipping non-existent document %@", doc.documentID];
                continue;
            }

            [[FIRCrashlytics crashlytics] logWithFormat:@"GetDataGetItemsInSpecificHome Processing(3) docID:%@", doc.documentID];                     

            NSDictionary *docData = nil;
            // 用@try-catch包裹data属性调用,尝试捕获可能的ObjC层面异常
            @try {
                // 一次性获取解析后的数据,避免重复调用触发多次解析
                docData = doc.data;
            } @catch (NSException *exception) {
                [[FIRCrashlytics crashlytics] recordException:exception];
                [[FIRCrashlytics crashlytics] logWithFormat:@"Failed to parse document %@: %@", doc.documentID, exception.reason];
                continue;
            }

            // 如果解析失败,跳过当前文档
            if (!docData) {
                [[FIRCrashlytics crashlytics] logWithFormat:@"Empty data for document %@", doc.documentID];
                continue;
            }

            for (NSString *key in keyArray) {                                  

                [[FIRCrashlytics crashlytics] logWithFormat:@"GetDataGetItemsInSpecificHome:%@ key:%@ Processing(4)", crashlyticsString, key];                                  

                NSMutableArray *arr = dataDict[key] ? [dataDict[key] mutableCopy] : [NSMutableArray array];                                  

                id object = docData[key];
                // 处理缺失的键值
                if (!object) {
                    object = [[[GeneralObject alloc] init] GenerateDefaultValueBasedOnKey:key];
                }                                                                          
                [arr addObject:object];                                                                        
                [dataDict setObject:arr forKey:key];  
            }
        }

        finishBlock(YES, dataDict); 
    }];
}

2. 进阶排查:指定时间戳解析行为

尝试使用dataWithServerTimestampBehavior:方法明确指定服务器时间戳的解析方式,避免默认解析逻辑的潜在问题:

// 替换doc.data的调用
docData = [doc dataWithServerTimestampBehavior:FIRServerTimestampBehaviorNone];

可选的行为包括:

  • FIRServerTimestampBehaviorNone: 保留原生的FIRTimestamp对象
  • FIRServerTimestampBehaviorEstimate: 转换为本地估计的日期
  • FIRServerTimestampBehaviorPrevious: 使用上次缓存的值

3. 环境升级

  • 将Firebase Firestore SDK升级到最新版本,排查是否是已知的SDK bug(部分旧版本存在特定数据类型解析的崩溃问题)
  • 确保Xcode和iOS SDK版本与Firebase SDK兼容

4. 全局异常捕获

由于崩溃属于底层C++错误,Objective-C的@try-catch无法捕获,可以添加全局异常和信号量捕获来收集更多信息:

// 在AppDelegate中添加
#import <signal.h>

void handleSignal(int signal) {
    // 收集崩溃信息并上报到Crashlytics
    NSString *signalName = [NSString stringWithFormat:@"Signal %d", signal];
    [[FIRCrashlytics crashlytics] logWithFormat:@"Crashed with signal: %@", signalName];
    [[FIRCrashlytics crashlytics] recordError:[NSError errorWithDomain:@"SignalCrash" code:signal userInfo:nil]];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 注册信号捕获
    signal(SIGABRT, handleSignal);
    signal(SIGSEGV, handleSignal);
    signal(SIGBUS, handleSignal);
    
    // 其他初始化代码
    return YES;
}

额外建议

  • 检查Firestore中该集合的文档,是否存在特殊数据类型(如未正确转换的GeoPoint、自定义对象、超大字段等)
  • 开启Firestore的调试日志,查看崩溃前的数据请求和解析细节:
// 在初始化Firestore时添加
[[FIRFirestore firestore] setLoggingEnabled:YES];

内容的提问来源于stack exchange,提问作者user2526720

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 08:32:15