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

mar*_*amx 2 core-data objective-c ios

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

  • __masterManagedObjectContext - >是具有NSPersistentStoreCoordinator的上下文,并将数据保存到磁盘.

  • _mainManagedObjectContext - >是应用程序使用的上下文,无处不在

  • dispatchContext - >在后台方法中使用的上下文,我有我的webservice访问权限和所有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;
}
Run Code Online (Sandbox Code Playgroud)

创建新商店cordinator的方法

- (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;

}
Run Code Online (Sandbox Code Playgroud)

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

@try
{
    dispatchContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    [dispatchContext setUndoManager:nil];
    [dispatchContext setParentContext:__masterManagedObjectContext];

    NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
    [notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:dispatchContext];

    if(dispatchContext !=  nil)
    {
        [NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
    }
    else
    {
        NSLog(@"context IS NIL");
    }
}
Run Code Online (Sandbox Code Playgroud)

背景方法:

- (void)parseDataWithObjects
{
    [dispatchContext lock];

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

    [dispatchContext save:&error];
    [dispatchContext unlock];
    [__masterManagedObjectContext save:nil];
}
Run Code Online (Sandbox Code Playgroud)

在所有UI中都可以使用此方法来访问coredata数据.

- (NSManagedObjectContext *)managedObjectContext
{
    return _mainManagedObjectContext;
}
Run Code Online (Sandbox Code Playgroud)

一个调用的例子:

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

...fetching, update, ...
Run Code Online (Sandbox Code Playgroud)

现在,我真正的问题:

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

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

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

我做错了什么?直到现在,这种情况让我感到困惑.有人可以帮帮我吗?我真的很感激 :)

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

继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];
    }];
}
Run Code Online (Sandbox Code Playgroud)

要开始我的数据导入(在后台),我使用此代码:

NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
Run Code Online (Sandbox Code Playgroud)

我的背景方法:

- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}
Run Code Online (Sandbox Code Playgroud)

恢复...

  • 第一 - 我创建了我的主要背景
  • 第二 - 我定义了背景上下文通知,以便在保存后合并更改.
  • 3 - 我称之为背景方法
  • 4rd - 我保存了我的背景背景

而且性能真的更好.但该应用冻结了一点,我认为是"mergeChanges".我做错了什么?

小智 6

当使用NSPrivateQueueConcurrencyTypeNSMainQueueConcurrencyType你应该包装你在这些上下文中所做的一切performBlock:.这可以确保这些命令在正确的队列中执行.例如,当您保存主上下文时:

[__masterManagedObjectContext performBlock:^{
    [__masterManagedObjectContext save];
}];
Run Code Online (Sandbox Code Playgroud)

此外,如果将其设置为主上下文的子项,则不必手动从调度上下文合并更改.保存子上下文后,更改将被推送到父上下文中.

另一个问题是您在一个线程上使用NSConfinementConcurrencyType初始化上下文,然后在另一个线程上使用它.使用此并发类型,在您要使用它的线程上初始化上下文非常重要.

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

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

[dispatchContext performBlock:^{
    [self parseDataWithObjects];
}];
Run Code Online (Sandbox Code Playgroud)

执行此操作时,无需获取锁,您在块中执行的所有操作都将在专用串行队列上处理.

性能

您已经提到过保存需要很长时间,并且您的应用无法响应.

如果您计划在调度上下文中导入大量数据,则具有三个嵌套上下文(私有< - main < - dispatch)的托管对象上下文设置不是最佳选择.这将始终阻塞主线程大量时间,因为您在调度上下文中所做的所有更改必须先复制到主上下文中,然后才能将它们保存在"根"上下文中.

我最近写了一篇关于此的文章,比较了不同核心数据设置的性能.在后续文章中,我更详细地解释了为什么这个设置在主线程上需要花费很多时间.

对于导入大量数据,使用具有公共持久性存储协调器的独立托管对象上下文要快得多.您可以创建一个上下文NSMainQueueConcurrencyType(用于所有与UI相关的内容),另一个用于NSPrivateQueueConcurrencyType(用于导入数据).您将相同的持久性存储协调器分配给它们.

// assuming you have persistendStoreCoordinator

NSManagedObjectContext* mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainContext.persistentStoreCoordinator = persistentStoreCoordinator;

NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = persistentStoreCoordinator;
Run Code Online (Sandbox Code Playgroud)

此设置不提供嵌套上下文所带来的自动更改传播,但通过保存通知很容易实现.注意performBlock:再次使用,以便合并发生在正确的线程上:

// ...
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
// ...

- (void)mergeChanges:(NSNotification*)notification {
    [mainContext performBlock:^{
        [mainContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!