Swift Testing测试Core Data时反复出现‘attempt to insert nil’错误求助
Core Data单元测试随机崩溃排查:-[__NSCFSet addObject:]: attempt to insert nil
我使用Swift Testing对Core Data交互进行单元测试,为避免影响SQL数据库,采用内存数据库替代。以下是实现persistentContainer变量的代码,通过环境变量在单元测试时将storeType设置为NSInMemoryStoreType:
lazy var persistentContainer: NSPersistentContainer = { let description = NSPersistentStoreDescription() description.type = storeType // Pass the data model filename to the container’s initializer. let container = NSPersistentCloudKitContainer(name: CoreDataStack.dataStoreName) if storeType == NSInMemoryStoreType { container.persistentStoreDescriptions = [description] } // 注:原代码省略了loadPersistentStores的实现部分 fatalError("Failed to load persistent stores: \(error.localizedDescription)") } }()
问题现象
该错误在完全不同的测试中随机出现,无法稳定复现,可能发生在保存、删除等操作时,或完全不出现。错误信息如下:
Thread 2: "-[__NSCFSet addObject:]: attempt to insert nil"
我怀疑是否因为Swift Testing在不同线程执行测试,均访问同一数据库导致?最新堆栈追踪如下:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null) *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil' *** First throw call stack: ( 0 CoreFoundation 0x00000001804b70ec __exceptionPreprocess + 172 1 libobjc.A.dylib 0x000000018008ede8 objc_exception_throw + 72 2 CoreFoundation 0x000000018053d62c -[__NSCFString characterAtIndex:].cold.1 + 0 3 CoreFoundation 0x000000018054627c -[__NSCFSet addObject:].cold.2 + 0 4 CoreFoundation 0x00000001805324dc -[__NSCFSet removeObject:] + 0 5 CoreData 0x0000000186d1d664 -[NSManagedObjectContext _processPendingDeletions:withInsertions:withUpdates:withNewlyForgottenList:withRemovedChangedObjects:] + 388 6 CoreData 0x0000000186d0bc28 -[NSManagedObjectContext _processRecentChanges:] + 1900 7 CoreData 0x0000000186d20e0c _performRunLoopAction + 376 8 CoreFoundation 0x000000018041a2ac __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 9 CoreFoundation 0x0000000180414cac __CFRunLoopDoObservers + 528 10 CoreFoundation 0x000000018041511c __CFRunLoopRun + 896 11 CoreFoundation 0x0000000180414960 CFRunLoopRunSpecific + 536 12 XCTestCore 0x000000010450a920 +[XCTWaiter _synchronouslyWaitForTimeInterval:] + 112 13 XCTestCore 0x000000010450b058 -[XCTWaiter _performWait:manager:completionHandler:] + 912 14 XCTestCore 0x0000000104509004 -[XCTWaiter _waitForExpectations:timeout:enforceOrder:synchronously:completionHandler:] + 520 15 XCTestCore 0x0000000104508dac -[XCTWaiter waitForExpectations:timeout:enforceOrder:] + 108 16 XCTestCore 0x00000001044d9898 __XCTAsyncEnumerateWithWaiter_block_invoke + 276 17 XCTestCore 0x00000001044d99fc __XCTAsyncEnumerate_block_invoke.98 + 52 18 XCTestCore 0x00000001044d999c __XCTAsyncEnumerateWithWaiter_block_invoke_2 + 44 19 XCTestCore 0x00000001044d874c __72-[XCTExecutionWorker enqueueTestIdentifiersToRun:testIdentifiersToSkip:]_block_invoke_3 + 124 20 XCTestCore 0x00000001044e55a8 -[XCTestRunSession executeTestsWithIdentifiers:skippingTestsWithIdentifiers:completion:] + 280 21 XCTestCore 0x00000001044d8690 __72-[XCTExecutionWorker enqueueTestIdentifiersToRun:testIdentifiersToSkip:]_block_invoke_2 + 120 22 XCTestCore 0x00000001044d9864 __XCTAsyncEnumerateWithWaiter_block_invoke + 224 23 XCTestCore 0x00000001044d99fc __XCTAsyncEnumerate_block_invoke.98 + 52 24 XCTestCore 0x00000001044d8510 XCTAsyncEnumerateWithWaiter + 500 25 XCTestCore 0x00000001044d82d0 __72-[XCTExecutionWorker enqueueTestIdentifiersToRun:testIdentifiersToSkip:]_block_invoke + 108 26 XCTestCore 0x00000001044d87c4 -[XCTExecutionWorker runWithError:] + 76 27 XCTestCore 0x00000001044d6808 __25-[XCTestDriver _runTests]_block_invoke.200 + 52 28 XCTestCore 0x00000001044ea5f8 -[XCTestObservationCenter _observeTestExecutionForTestBundle:inBlock:] + 180 29 XCTestCore 0x00000001044d61f4 -[XCTestDriver _runTests] + 1088 30 XCTestCore 0x00000001044d7f90 _XCTestMain + 116 31 libXCTestBundleInject.dylib 0x0000000102c5b168 __RunTests_block_invoke_2 + 0 32 CoreFoundation 0x00000001803e3d88 __CFMachPortPerform + 172 33 CoreFoundation 0x000000018041ba6c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 56 34 CoreFoundation 0x000000018041b034 __CFRunLoopDoSource1 + 496 35 CoreFoundation 0x0000000180415628 __CFRunLoopRun + 2188 36 CoreFoundation 0x0000000180414960 CFRunLoopRunSpecific + 536 37 GraphicsServices 0x0000000190183b10 GSEventRunModal + 160 38 UIKitCore 0x0000000185aa2b40 -[UIApplication _run] + 796 39 UIKitCore 0x0000000185aa6d38 UIApplicationMain + 124 40 EasyFPU.debug.dylib 0x000000010390da14 __debug_main_executable_dylib_entry_point + 64 41 dyld 0x0000000102d79410 start_sim + 20 42 ??? 0x0000000102efa274 0x0 + 4344226420 )
排查思路与解决提示
- 隔离测试用例的Core Data栈:当前
persistentContainer是懒加载单例,多个测试线程共享同一栈会引发线程安全问题。每个测试用例应在setUp()中初始化独立的内存Core Data栈,测试结束后在tearDown()中销毁,彻底避免测试间的状态污染。 - 检查
NSManagedObjectContextObjectsDidChangeNotification观察者:堆栈明确指出问题出在该通知的观察者逻辑中。检查所有注册该通知的代码,确保在处理变更集合(如deletedObjects、insertedObjects)时,没有尝试往Set中插入nil对象。比如是否存在对象已被释放但仍被加入集合的情况。 - 严格遵守Core Data线程规则:
NSManagedObjectContext并非线程安全,每个线程必须使用专属的context。测试中若存在异步操作,必须用performAndWait或perform包裹所有context访问逻辑,避免跨线程操作。 - 启用Core Data并发调试:在Xcode Scheme的Arguments中添加
-com.apple.CoreData.ConcurrencyDebug 1,强制Core Data检测线程违规操作,直接定位多线程访问的问题点。 - 验证数据模型与对象状态:检查数据模型中的必填约束,确保所有对象在保存/删除时状态合法。同时排查是否存在ManagedObject被提前释放,导致后续变更处理时出现nil引用的情况。
- 简化测试复现路径:将随机崩溃的测试用例单独提取,逐步移除依赖项,尝试稳定复现问题。或使用XCTest的
-only-testing参数仅运行可疑测试,缩小排查范围。
内容的提问来源于stack exchange,提问作者Ulrich
相关产品推荐
相关产品推荐

