我正在敲打我的脑袋,我正在使用Core Data作为SQLLite数据库,我能够成功保存到数据库(我已经在离线SQLLite浏览器中检查了内容),但是保存我尝试运行的第一个查询后回来看到下面的错误,我在互联网上找不到与此特定错误相关的任何有用信息:
核心数据:错误:-executeRequest:遇到异常= 数据库显示已损坏。 (userInfo = {无效的主键) NSFilePath =“/ Users / user / Library / Application Support / iPhone Simulator / 7.0.3 / Documents / db.sqlite”;
这里的问题是导致此错误的原因,因为我找不到任何与之相关的信息。
对于一些小背景,这是我的设置,请假设我已经有充分的理由进行设计并且没有提供“改变你的设计”的答案,除非你能看到一些从根本上打破的东西模式本身。
我有3个托管对象上下文,它们都是NSPrivateQueueConcurrencyType,第一个(A)附加到持久性存储协调器,第二个(B)有A集作为其父上下文,第三个(C)有B设置为它的父上下文 - 链。原因是C是一个可写上下文,从网络源获取数据并同步并保存它,B是UI元素共享的上下文,我希望它是响应的,最后A是设计的背景上下文卸载任何延迟保存到磁盘关闭上下文B& ç
PSC< -A< -B< -C
如果我取出最后一步(将A保存到PSC),则应用程序运行良好,将所有内容保存在内存中并查询内存中的上下文。崩溃仅在我将保存步骤添加回来后才会发生,并且仅在保存后针对数据库运行的第一个查询运行时发生。我的Save和我的fetch执行都包含在performBlock中:
这是最后一次保存:
- (void)deepSave
{
// Save to the Save Context which happens in memory, so the actual write to disk operation occurs on background thread
// Expects to be called with performBlock
NSError *error = nil;
[super save:&error];
NSAssert(!error, error.localizedDescription);
// Trigger the save context to save to disk, operation will be queued and free up read only context
NSManagedObjectContext *saveContext = self.parentContext;
[saveContext performBlock:^{
NSError *error = nil;
[saveContext save:&error];
NSAssert(!error, error.localizedDescription);
}];
}
这是执行堆栈(在NSManagedObjectContext队列线程上)
#0 0x0079588a in objc_exception_throw ()
#1 0x079d98e7 in -[NSSQLiteConnection handleCorruptedDB:] ()
#2 0x078d9b8d in -[NSSQLiteConnection fetchResultSet:usingFetchPlan:] ()
#3 0x078e24a5 in newFetchedRowsForFetchPlan_MT ()
#4 0x078cd48e in -[NSSQLCore newRowsForFetchPlan:] ()
#5 0x078cca8d in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#6 0x078cc53f in -[NSSQLCore executeRequest:withContext:error:] ()
#7 0x078cbf62 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#8 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#9 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#10 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#11 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#12 0x013c34b0 in _dispatch_client_callout ()
#13 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#14 0x013b0422 in dispatch_barrier_sync_f ()
#15 0x0791e2a2 in _perform ()
#16 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#17 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#18 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#19 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#20 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#21 0x013c34b0 in _dispatch_client_callout ()
#22 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#23 0x013b0422 in dispatch_barrier_sync_f ()
#24 0x0791e2a2 in _perform ()
#25 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#26 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
答案 0 :(得分:21)
好的,我已经找到了它。似乎在 propertiesToFetch 中针对NSManagedObject resultType(不应该使用,我们的错误)在此设置中有一些内容 - 针对具有父上下文而不是持久协调器的上下文。此单元测试显示您所要做的就是设置一个属性来获取以获取此错误(在执行查询时,没有要获取的属性正常工作)。我们这里的解决方法是停止使用属性获取错误:)
- (void)testManagedObjectContextDefect
{
NSManagedObjectContext *contextA = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
contextA.persistentStoreCoordinator = sqllitePersistentStoreCoordinator;
NSManagedObjectContext *contextB = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
contextB.parentContext = contextA;
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"GCSCObject" inManagedObjectContext:contextB];
[contextB performBlockAndWait:^{
GCSCObject *object = [[GCSCObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:contextB];
object.serverID = @"1";
NSError *error = nil;
XCTAssert([contextB save:&error] && !error, @"Failed to save - %@",error); // B -> A save
}];
[contextA performBlock:^{
NSError *error = nil;
XCTAssert([contextA save:&error] && !error, @"Failed to save - %@",error); // A -> PSC, background save
}];
[contextB performBlockAndWait:^{
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
NSError *error = nil;
NSArray *results = [contextB executeFetchRequest:request error:&error];
XCTAssert(results.count == 1 && !error, @"Fetch failed to retrieve - %@ / %@",results,error);
GCSCObject *object = results[0];
XCTAssert([object.serverID isEqualToString:@"1"], @"Value retrieval failed");
// Everything passes up to here, so far so good!
request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
request.propertiesToFetch = @[@"serverID"]; // This is the culprit of the index crash
results = [contextB executeFetchRequest:request error:&error];
XCTAssert(!error, @"%@", error.localizedDescription); // !!! HERE we have a failure, assert: "Core Data: error: -executeRequest: encountered exception = The database appears corrupt. (invalid primary key) with userInfo = { NSFilePath = "/path/db.sqlite }";
}];
}
在这种情况下,GCSCObject是一个常规实体,serverID是它的参数之一(使用哪个参数无关紧要,或者它是什么类型,我试过多个。这是我用于此测试的serverID参数的说明:
崩溃发生了或者没有我们提供和上下文保存(虽然这样做会使得保存后台队列无效)
我喜欢关于为什么会出现这种情况的反馈,但就目前而言,不使用属性获取可以让我们的应用程序顺利运行。我正在考虑在这里提交Apple Bug。
答案 1 :(得分:9)
如果在执行正在检索某些聚合结果(sum,max,min,...)的获取请求时遇到此错误,请确保设置
fetchRequest.resultType = NSDictionaryResultType;
答案 2 :(得分:4)
首先,您的UI上下文不应该是私有队列上下文。这就是NSMainQueueConcurrencyType
的用途。
其次,不要在保存时检查错误。查看BOOL
的{{1}}返回。即使成功保存,该错误也可能存在垃圾。
第三,你的其他两个保存是什么样的?如果它们都在私有队列中并被保存为异步,那么您可能会遇到竞争状态。 C应该同步保存,B应该同步保存,然后A应该是异步的。