如何修复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异常)。
代码问题点
当前代码存在以下风险:
- 未检查
FIRDocumentSnapshot是否存在:直接遍历snapshot.documents但未判断doc.exists,可能处理无效文档 - 重复调用
doc.data属性:每次调用都会触发Firestore的数据解析逻辑,增加崩溃概率 - 未处理数据解析时的异常:直接访问
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
相关产品推荐
相关产品推荐

