我们遇到了许多与核心数据相关的并发相关崩溃和我们的应用删除,所以我创建了一个小项目来重现其中一个场景。 在以下场景中,我能够重现“CoreData无法解决故障”的崩溃: - 我有2个子上下文A,B,都与相同的主要父内容相关联。 - coredata模型非常简单,一个ConferenceRoom对象有很多Line对象。 - 上下文A和B具有并发类型“NSPrivateQueueConcurrencyType”,父类型为“NSMainQueueConcurrencyType” - 线程1从子上下文A中获取对象,对其进行故障,在上下文A和主父上下文中删除它 - 线程2从子上下文B中获取相同的对象,等待2秒,将其保存在上下文B和主父上下文中。
- >当尝试保存到子上下文B时,应用程序在thread2中崩溃并且“CoreData无法完成错误”
请注意,在尝试新的xcode6 coredata调试标志后,我们已经修复了问题:“ - com.apple.CoreData.ConcurrencyDebug 1”,因此理论上没有线程警告/问题...... 那么有谁可以解释我们如何避免这些崩溃? (如果需要,我可以发送完整的项目。)
这是代码(我是新手ios开发人员,肯定是快速/脏代码)
核心方法:
//this creates a Conference and a Line object (called from AppDelegate when app launches
- (void) testCrash
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"dd-MM HH:mm:ss"];
NSString *initConfName = [formatter stringFromDate:[NSDate date]];
//fix
[self.managedObjectChildContext performBlockAndWait:^{
ConferenceRoom *room = [NSEntityDescription insertNewObjectForEntityForName:@"ConferenceRoom" inManagedObjectContext:self.managedObjectChildContext];
room.name = initConfName;
Line *line1 = [NSEntityDescription insertNewObjectForEntityForName:@"Line" inManagedObjectContext:self.managedObjectChildContext];
line1.phoneNumber = @"4154243243";
NSMutableSet *lines = [room mutableSetValueForKey:@"lines"];
[lines addObject:line1];
}];
[self saveChildContext];
[self saveContext];
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(mainThread1:)
object:initConfName];
[myThread start];
NSThread* myThread2 = [[NSThread alloc] initWithTarget:self
selector:@selector(mainThread2:)
object:initConfName];
[myThread2 start];
}
- (void) mainThread1:(NSString *) initConfName
{
NSLog(@"started thread 1");
//GET OBJ FROM CHILD CONTEXT 1
[self.managedObjectChildContext performBlockAndWait:^{
NSArray *results = [self getConfRoom: self.managedObjectChildContext withName:initConfName];
NSLog(@"C1 conf:%@", results);
ConferenceRoom *roomFoundChild1 = [results lastObject];
NSArray *linesc1 = [[roomFoundChild1 mutableSetValueForKey:@"lines"] allObjects];
Line *linec1 = [linesc1 firstObject];
NSLog(@"LINEC1=%@", linec1);
//DELETE CONF IN CHILD CONTEXT 1:
NSLog(@"Thread1:going to delete conference %@", roomFoundChild1);
[self.managedObjectChildContext deleteObject: roomFoundChild1];
}];
NSLog(@"Thread1: before saving child context");
[self saveThisContext: self.managedObjectChildContext];
NSLog(@"Thread1: before saving main context");
//test: save in main context, works without this
[self saveContext];
}
- (void) mainThread2:(NSString*) initConfName
{
NSLog(@"started thread 2");
//GET OBJ FROM CHILD CONTEXT 2
__block NSArray *results;
__block ConferenceRoom *roomFoundChild2;
__block NSString *newName;
[self.managedObjectChildTwoContext performBlockAndWait:^{
results = [self getConfRoom: self.managedObjectChildTwoContext withName:initConfName];
NSLog(@"C2 conf\n:%@", results);
roomFoundChild2 = [results lastObject];
NSString *n = roomFoundChild2.name;
//UPDATE CONF ROOM IN CHILD CONTEXT 2
newName = [NSString stringWithFormat:@"%@-%@", initConfName, @"newName2"];
NSLog(@"Thread 2 waiting");
[NSThread sleepForTimeInterval:2];
NSLog(@"Thread 2 resuming");
roomFoundChild2.name = newName;
NSLog(@"roomFoundChild2, %@", roomFoundChild2);
}];
NSLog(@"Thread2: before saving child context");
[self saveThisContext:self.managedObjectChildTwoContext];
NSLog(@"Thread2: after saving to child context");
results = [self getConfRoom:self.managedObjectChildTwoContext withName:newName];
NSLog(@"C2 context after delete:%@", results);
NSLog(@"Thread2: before saving main context");
//test: save in main context, works without this
[self saveContext];
}
- (void)saveContext
{
// NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
[managedObjectContext performBlockAndWait:^{
if ([managedObjectContext hasChanges]) {
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved ERROR %@, %@", error, [error userInfo]);
abort();
};
}
}];
}
}
- (void)saveChildContext
{
// NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectChildContext;
if (managedObjectContext != nil) {
//COREDATAFLAG CHANGE
[managedObjectContext performBlockAndWait:^{
if ([managedObjectContext hasChanges]) {
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved ERROR %@, %@", error, [error userInfo]);
abort();
};
}
}];
}
}
- (void) saveThisContext: (NSManagedObjectContext *)ctx
{
if (ctx != nil) {
[ctx performBlockAndWait:^{
if ([ctx hasChanges]) {
NSError *error = nil;
if (![ctx save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved ERROR %@, %@", error, [error userInfo]);
//removed abort to test..
//abort();
};
}
}];
}
}
答案 0 :(得分:0)
//test: save in main context, works without this
[self saveContext];
您无法从线程2调用该方法,因为它正在保存主线程上下文,只能从主线程保存/编辑/等。有几种方法可以解决这个问题,但您可以做的一件事是[self performSelectorOnMainThread:@selector(saveContext)];