在NSManagedObject上安全地解包可选项时的EXC_BAD_ACCESS

时间:2018-03-08 08:32:00

标签: ios swift core-data exc-bad-access

我的应用程序中有以下基础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
    }
}

我也有一些这样的子类。它们也被定义为DataModel中的子类:

@objc(Answer)
class Answer: LaravelEntity {

}

我尝试获取给定类的最后更新实体,如下所示:

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在调试器中返回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次迭代中崩溃。

以下是运行上述测试时的callstack:

#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上(随机)崩溃。

1 个答案:

答案 0 :(得分:0)

核心数据不是线程安全的。每个上下文都有一个且只有一个可以读取或写入的线程。如果您违反此行为,则行为将不明确。这意味着它可能会崩溃,也可能不会崩溃。它可能会等待10分钟,然后当您执行不相关的操作时会崩溃。

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

此外,self.

还有额外的lastUpdatedRequest