EXC_BAD_ACCESS 在 NSManagedObject 上安全解包可选时

EXC_BAD_ACCESS when safely unwrapping optional on NSManagedObject

我的应用程序中有以下基础 NSManagedObject:

@objc(LaravelEntity)
class LaravelEntity: NSManagedObject {

    static func create(moc: NSManagedObjectContext) -> LaravelEntity? {
        let entityName = NSStringFromClass(self)
        let ent = NSEntityDescription.insertNewObject(forEntityName: entityName, into: moc) as? LaravelEntity
        // We set local changes to true, so it will definitely be pushed to the server
        ent?.local_changes = true
        return ent
    }

    static func lastUpdatedRequest() -> NSFetchRequest<LaravelEntity> {
        let request : NSFetchRequest<LaravelEntity> = self.self.fetchRequest()
        let sort = NSSortDescriptor(key: #keyPath(LaravelEntity.updated_at), ascending: false)
        request.sortDescriptors = [sort]
        request.fetchLimit = 1
        return request
    }
}

我也有一些子class这个。它们在 DataModel 中也被定义为 subclasses:

@objc(Answer)
class Answer: LaravelEntity {

}

我尝试像这样获取给定 class 的最后更新的实体:

func lastUpdated(klass: LaravelEntity.Type, ctx : NSManagedObjectContext? = nil) -> LaravelEntity? {
        do {
            let request = klass.lastUpdatedRequest()
            let result = try ctx?.fetch(request) ?? self.persistentContainer.newBackgroundContext().fetch(request)
            return result.first
        } catch {
            print("Error while fetching last updated: \(error)!")
        }

        return nil
    }

这在大多数情况下都很好用。然而,当尝试做一些像查询 id 这样简单的事情时,应用程序偶尔会崩溃:lastUpdated?.id == 1。我不知道为什么这会崩溃,因为所有可选值都已安全处理(据我所知)。此外,在调试器中查看对象时,它们显示正常(例如 po lastUpdated?.id == 1 returns true 在调试器中)。

我创建了以下示例测试用例来重现该问题,有些一致:

func testLastUpdated() {
        for i in 0...1000 {
            let cases = [Location.self, Position.self, Answer.self, Question.self]
            for kase in cases {
                let old = kase.create(moc: sm.persistentContainer.viewContext)
                let new = kase.create(moc: sm.persistentContainer.viewContext)
                XCTAssert(old != nil, "Couldn't create old \(kase) object!")
                XCTAssert(new != nil, "Couldn't create new \(kase) object!")
                old?.updated_at = Date(timeIntervalSinceNow: TimeInterval(-60*60*24 + i * 60))
                old?.id = Int32(i*2)
                new?.updated_at = Date(timeIntervalSinceNow: TimeInterval(i*60))
                new?.id = Int32(i*2+1)
                try! sm.persistentContainer.viewContext.save()
                let last_updated = sm.lastUpdated(klass: kase)
                XCTAssert(last_updated?.id == new?.id, "Last updated \(kase) doesn't have the correct id \(last_updated?.id ?? -1) vs \(new?.id ?? -1)!") //It crashes on this line.
            }
        }
    }

然而,即使有 1000 次重复,它也只崩溃了我 运行 测试时间的大约 1/3。此外,它只会在低 50 次迭代时崩溃。

下面是运行上面测试时的调用栈:

#0  0x00000001857f8430 in objc_msgSend ()
#1  0x0000000188e1f274 in -[NSPersistentStoreCoordinator _canRouteToStore:forContext:] ()
#2  0x0000000188e21f38 in __110-[NSPersistentStoreCoordinator(_NSInternalMethods) newValueForRelationship:forObjectWithID:withContext:error:]_block_invoke ()
#3  0x0000000188e29af0 in gutsOfBlockToNSPersistentStoreCoordinatorPerform ()
#4  0x0000000185f19048 in _dispatch_client_callout ()
#5  0x0000000185f5b760 in _dispatch_sync_invoke_and_complete_recurse ()
#6  0x0000000185f5b26c in _dispatch_sync_wait ()
#7  0x0000000188e17f74 in _perform ()
#8  0x0000000188e17d2c in -[NSPersistentStoreCoordinator _routeLightweightBlock:toStore:] ()
#9  0x0000000188d4bc94 in -[NSPersistentStoreCoordinator(_NSInternalMethods) newValueForRelationship:forObjectWithID:withContext:error:] ()
#10 0x0000000188d34d7c in _PFFaultHandlerFulfillFault ()
#11 0x0000000188d32a80 in _PFFaultHandlerLookupRow ()
#12 0x0000000188d32334 in _PF_FulfillDeferredFault ()
#13 0x0000000188dd7644 in _pvfk_header ()
#14 0x0000000188dd7450 in _sharedIMPL_pvfk_core_i ()
#15 0x00000001092484a4 in implicit closure #5 in SyncManagerTests.testLastUpdated() at CDTests.swift:74
#16 0x00000001092589d4 in partial apply for implicit closure #5 in SyncManagerTests.testLastUpdated() ()
#17 0x00000001092a5bdc in partial apply for closure #1 in XCTAssertTrue(_:_:file:line:) ()
#18 0x00000001092a5448 in partial apply for closure #1 in _XCTRunThrowableBlock(_:) ()
#19 0x0000000109290224 in thunk for @callee_owned () -> () ()
#20 0x00000001092a6048 in _XCTRunThrowableBlockBridge ()
#21 0x00000001092943bc in specialized _XCTRunThrowableBlock(_:) ()
#22 0x0000000109296118 in specialized XCTAssertTrue(_:_:file:line:) ()
#23 0x000000010929045c in XCTAssert(_:_:file:line:) ()
#24 0x0000000109248010 in SyncManagerTests.testLastUpdated() at CDTests.swift:74
#25 0x0000000109248aa8 in @objc SyncManagerTests.testLastUpdated() ()
#26 0x000000018659d670 in __invoking___ ()
#27 0x000000018647c6cc in -[NSInvocation invoke] ()
#28 0x0000000107996654 in __24-[XCTestCase invokeTest]_block_invoke.275 ()
#29 0x00000001079e4208 in -[XCTMemoryChecker _assertInvalidObjectsDeallocatedAfterScope:] ()
#30 0x0000000107996404 in __24-[XCTestCase invokeTest]_block_invoke ()
#31 0x00000001079dc9d8 in -[XCUITestContext performInScope:] ()
#32 0x000000010799614c in -[XCTestCase invokeTest] ()
#33 0x0000000107997224 in __26-[XCTestCase performTest:]_block_invoke.382 ()
#34 0x00000001079e1a78 in +[XCTContext runInContextForTestCase:block:] ()
#35 0x0000000107996c20 in -[XCTestCase performTest:] ()
#36 0x0000000107992e14 in __27-[XCTestSuite performTest:]_block_invoke ()
#37 0x000000010799283c in -[XCTestSuite _performProtectedSectionForTest:testSection:] ()
#38 0x0000000107992a4c in -[XCTestSuite performTest:] ()
#39 0x0000000107992e14 in __27-[XCTestSuite performTest:]_block_invoke ()
#40 0x000000010799283c in -[XCTestSuite _performProtectedSectionForTest:testSection:] ()
#41 0x0000000107992a4c in -[XCTestSuite performTest:] ()
#42 0x0000000107992e14 in __27-[XCTestSuite performTest:]_block_invoke ()
#43 0x000000010799283c in -[XCTestSuite _performProtectedSectionForTest:testSection:] ()
#44 0x0000000107992a4c in -[XCTestSuite performTest:] ()
#45 0x00000001079eb484 in __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke ()
#46 0x00000001079a5994 in -[XCTestObservationCenter _observeTestExecutionForBlock:] ()
#47 0x00000001079eb300 in -[XCTTestRunSession runTestsAndReturnError:] ()
#48 0x00000001079823d4 in -[XCTestDriver runTestsAndReturnError:] ()
#49 0x00000001079e0c20 in _XCTestMain ()
#50 0x000000018653e0fc in __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ ()
#51 0x000000018653d9cc in __CFRunLoopDoBlocks ()
#52 0x000000018653b6dc in __CFRunLoopRun ()
#53 0x000000018645bfb8 in CFRunLoopRunSpecific ()
#54 0x00000001882f3f84 in GSEventRunModal ()
#55 0x000000018fa302e8 in UIApplicationMain ()
#56 0x000000010137f528 in main at AppDelegate.swift:13
#57 0x0000000185f7e56c in start ()

我不知道是什么导致了这样的崩溃,因为所有可选值都得到了正确处理。

编辑 1: 所以崩溃肯定发生在下一行 last_updated?.id == new?.id (这对我来说仍然没有任何意义)。即使我注释掉所有更改对象的代码并只创建一个对象,它仍然会在 lastUpdated?.id != nil.

上(随机地)崩溃

核心数据不是线程安全的。每个上下文都有一个且只有一个线程可以从中读取或写入。如果您违反此规定,则该行为是未定义的。这意味着它可能会崩溃,也可能不会。它可能会等待 10 分钟,然后当你做一些不相关的事情时崩溃。

在您的代码中,您在 func lastUpdated 中创建了一个 newBackgroundContext(因为您传递的是 nil 上下文)。使用此上下文进行提取是非法的。您可以 将此上下文与 performBlockperformBlockAndWait 一起使用。

你在lastUpdatedRequest

中还有一个额外的self.