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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:09:50