Ios CoreData与3 MOC解决方案(应用程序在保存过程中冻结)

Ios CoreData与3 MOC解决方案(应用程序在保存过程中冻结),ios,objective-c,core-data,Ios,Objective C,Core Data,我在我的应用程序中使用coredata,有3个上下文: \uu masterManagedObjectContext->是具有NSPersistentStoreCoordinator并将数据保存到磁盘的上下文 \u mainManagedObjectContext->是应用程序在任何地方使用的上下文 dispatchContext->后台方法中使用的上下文,在这里我可以访问Web服务和所有coredata插入/更新内容 我将编写一些代码来实现我的解决方案: 应用程序初始化代码: - (BOO

我在我的应用程序中使用coredata,有3个上下文:

  • \uu masterManagedObjectContext->是具有NSPersistentStoreCoordinator并将数据保存到磁盘的上下文

  • \u mainManagedObjectContext->是应用程序在任何地方使用的上下文

  • dispatchContext->后台方法中使用的上下文,在这里我可以访问Web服务和所有coredata插入/更新内容

我将编写一些代码来实现我的解决方案:

应用程序初始化代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //a app começa aqui
{        
    NSPersistentStoreCoordinator *coordinator = [self newPersistentStoreCoordinator];
    __masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [__masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

    _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_mainManagedObjectContext setUndoManager:nil];
    [_mainManagedObjectContext setParentContext:__masterManagedObjectContext];

    return YES;
}
方法创建新的存储协调器

- (NSPersistentStoreCoordinator *)newPersistentStoreCoordinator
{
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];

    NSError *error = nil;
    NSPersistentStoreCoordinator *pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self newManagedObjectModel]];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return pC;
}

- (NSManagedObjectModel *)newManagedObjectModel
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return newManagedObjectModel;

}
具有上下文规范的线程调用(基本代码):

背景法:

- (void)parseDataWithObjects
{
    [dispatchContext lock];

    ...
    webservice data parse, and core data inserting/updating (+/- 5MB)
    ...

    [dispatchContext save:&error];
    [dispatchContext unlock];
    [__masterManagedObjectContext save:nil];
}
此方法在所有用户界面中进行缩放,以访问coredata数据

- (NSManagedObjectContext *)managedObjectContext
{
    return _mainManagedObjectContext;
}
例如:

NSManagedObjectContext *context =  [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];

...fetching, update, ...
现在,我真正的问题是:

每当要保存主上下文(
[\u masterManagedObjectContext save:nil];
,在后台),当我尝试访问主上下文(
\u mainManagedObjectContext
)时,应用程序就会冻结(可能是锁定?)

保存过程需要很长时间(因为有大量数据(大约6mb))。保存时,应用程序会变慢,如果我在运行此过程时访问一些数据,我的应用程序将永远冻结(我需要强制退出)

另一个问题是合并上下文。想象一下,在其他viewController中使用主上下文并保存该上下文,在我关闭de应用程序之前,一切都正常。当我再次打开应用程序时,没有保存任何内容

我做错了什么?到目前为止,这种情况让我感到困惑。 有人能帮我吗?我真的很感激:)

---------- 编辑:

-(void) createContexts
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
    NSError *error = nil;
    pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    mainManagedObjectContext.persistentStoreCoordinator = pC;

}

- (void)mergeChanges:(NSNotification*)notification {
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void)saveMasterContext
{
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext save:nil];
    }];
}
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}
根据Florian Kugler的回答,现在我只有2个上下文,每个上下文都有相同的协调器

当我的应用程序初始化时,我调用此方法:

-(void) createContexts
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
    NSError *error = nil;
    pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    mainManagedObjectContext.persistentStoreCoordinator = pC;

}

- (void)mergeChanges:(NSNotification*)notification {
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void)saveMasterContext
{
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext save:nil];
    }];
}
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}
要开始数据导入(在后台),我使用以下代码:

-(void) createContexts
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
    NSError *error = nil;
    pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    mainManagedObjectContext.persistentStoreCoordinator = pC;

}

- (void)mergeChanges:(NSNotification*)notification {
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void)saveMasterContext
{
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext save:nil];
    }];
}
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}
我的背景方法:

-(void) createContexts
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
    NSError *error = nil;
    pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    mainManagedObjectContext.persistentStoreCoordinator = pC;

}

- (void)mergeChanges:(NSNotification*)notification {
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void)saveMasterContext
{
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext save:nil];
    }];
}
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}
恢复

  • 1-我创建我的主上下文
  • 第二个-我定义后台上下文通知,以合并更改 保存之后
  • 第三,我调用后台方法
  • 第4步-我保存我的背景上下文

而且性能确实更好。但应用程序有点冻结,我想是在“合并更改”。我做错了什么?

当使用
NSPrivateQueueConcurrencyType
NSMainQueueConcurrencyType
时,您应该将在这些上下文中执行的所有操作都包装在
performBlock:
中。这可以确保这些命令在正确的队列上执行。例如,保存主上下文时:

[__masterManagedObjectContext performBlock:^{
    [__masterManagedObjectContext save];
}];
此外,如果将调度上下文设置为主上下文的子上下文,则不必手动合并调度上下文中的更改。保存子上下文后,更改将被推送到父上下文中

另一个问题是,您正在一个线程上初始化具有NSConfinementConcurrencyType的上下文,然后在另一个线程上使用它。对于这种并发类型,在要使用它的线程上初始化上下文是非常重要的

但是,我建议您根本不要使用NSConfinementConcurrencyType。一种可能的替代方法是使用
NSPrivateQueueConcurrencyType
设置调度上下文:

NSManagedObjectContext* dispatchContext = [[NSManagedObjectContext] alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
dispatchContext.parentContext = __masterManagedObjectContext;

[dispatchContext performBlock:^{
    [self parseDataWithObjects];
}];
执行此操作时,无需获取锁,在块内执行的所有操作都将在专用串行队列上处理

演出 您已经提到,保存需要很长时间,并且您的应用程序会变得无响应


你的托管对象上下文设置有三个嵌套上下文(private Yes!你的博客非常好,让我了解上下文是如何工作的。我用你的建议编辑了我的问题,你能看看我的代码吗?谢谢你的好回答:)你的博客关闭了吗?@MycroftCanner看起来他关闭了他的博客,但是你绝对应该阅读他在objc.io上的核心数据书——这本书非常好。你不需要跳
[NSThread detachNew…]
舞。您只需使用
NSPrivateQueueConcurrencyType
初始化后台上下文,然后执行`[backgroundContext performBlock:^{…您的导入代码…}]。您在此块中执行的所有操作都将在后台线程上执行。为了减少合并的影响,您应该定期保存后台MOC。不要导入所有内容然后保存,而是每100-200个对象保存一次。所以,我的所有背景代码都需要在[backgroundContext performBlock:^{…您的导入代码…}]中?我需要在NSPrivateQueueConcurrencyType中调用我的后台方法?是的,它需要在内部,但当然不是字面意义上的,您可以从块中调用一个方法。块本身将在后台执行,因此在本例中,要获得并发性,只需对后台上下文执行
performBlock:
调用即可。如果我理解,每100/200个对象,我就对后台上下文执行一次“保存”。每次保存时,是否与主上下文合并?这是一个好主意?我试着保存每200个对象,所以我有同样的问题。。用户界面变慢,但有些滞后:/