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 上下文)。使用此上下文进行提取是非法的。您可以 仅 将此上下文与 performBlock
或 performBlockAndWait
一起使用。
你在lastUpdatedRequest
中还有一个额外的self.
我的应用程序中有以下基础 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 上下文)。使用此上下文进行提取是非法的。您可以 仅 将此上下文与 performBlock
或 performBlockAndWait
一起使用。
你在lastUpdatedRequest
self.