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

Posted

技术标签:

【中文标题】带有 3 MOC 解决方案的 Ios CoreData(应用程序在保存过程中冻结)【英文标题】:Ios CoreData with 3 MOC solution (app freezes on saving process) 【发布时间】:2013-05-21 09:34:55 【问题描述】:

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

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

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

dispatchContext -> 后台方法中使用的上下文,我可以在其中访问我的 Web 服务和所有核心数据插入/更新内容。

我会放一些代码来实现我的解决方案:

应用初始化代码:

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


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

@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");
    

后台方法:

- (void)parseDataWithObjects

    [dispatchContext lock];

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

    [dispatchContext save:&error];
    [dispatchContext unlock];
    [__masterManagedObjectContext save:nil];

在所有 UI 中调用此方法以访问 coredata 数据。

- (NSManagedObjectContext *)managedObjectContext

    return _mainManagedObjectContext;

调用示例:

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

...fetching, update, ...

现在,我真正的问题:

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

保存过程需要很长时间(因为数据量很大(大约 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];
    ];

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

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

正在恢复中...

第一个 - 我创建我的主要上下文 2nd - 我定义了后台上下文通知,以合并更改 保存后。 3rd - 我调用后台方法 4rd - 我保存了我的背景上下文

而且性能确实更好。但是该应用程序冻结了一点,我认为是在“mergeChanges”上。我做错了什么?

【问题讨论】:

你不需要跳 [NSThread detachNew...] 舞蹈。您可以使用 NSPrivateQueueConcurrencyType 初始化背景上下文,然后执行 `[backgroundContext performBlock:^ ...your importing code... ]。您在此块中所做的一切都将在后台线程上执行。为了减少合并的影响,您应该定期保存后台 MOC。不要导入所有内容然后保存,而是每 100-200 个对象保存一次。 Soo,我所有的后台代码都需要在 [backgroundContext performBlock:^ ...您的导入代码... ] 内?我需要在 NSPrivateQueueConcurrencyType 中调用我的后台方法? 是的,它需要在里面,但当然不是字面意思,你可以从块中调用一个方法。块本身将在后台执行,因此在这种情况下,您只需在后台上下文中调用performBlock: 即可获得并发。 如果我理解的话,每 100/200 个对象,我都会在背景上下文中“保存”一次。每次保存时,是否与主上下文合并?这是个好主意吗? 我已经尝试保存每 200 个对象,所以我遇到了同样的问题.. 用户界面变慢,有一些滞后:/ 【参考方案1】:

当使用NSPrivateQueueConcurrencyTypeNSMainQueueConcurrencyType 时,您应该将在这些上下文中所做的所有事情都包含在performBlock: 中。这可以确保这些命令在正确的队列上执行。例如,当您保存主上下文时:

[__masterManagedObjectContext performBlock:^
    [__masterManagedObjectContext save];
];

此外,如果您将调度上下文设置为主上下文的子级,则不必手动从调度上下文中合并更改。保存子上下文后,更改将立即推送到父上下文中。

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

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

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

[dispatchContext performBlock:^
    [self parseDataWithObjects];
];

当您执行此操作时,无需获取锁,您在块内执行的所有操作都将在私有串行队列上处理。

性能

您已经提到保存需要很长时间并且您的应用变得无响应。

如果您计划在调度上下文中导入大量数据,则使用三个嵌套上下文(私有

我最近写了一篇关于这个的文章,comparing the performance of different core data setups。在follow up post 中,我更详细地解释了为什么此设置需要在主线程上花费这么多时间。

对于导入大量数据,使用独立的托管对象上下文和公共持久存储协调器会快得多。您使用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;

此设置不提供通过嵌套上下文获得的自动更改传播,但通过保存通知很容易做到这一点。注意再次使用performBlock:,以便合并发生在正确的线程上:

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

- (void)mergeChanges:(NSNotification*)notification 
    [mainContext performBlock:^
        [mainContext mergeChangesFromContextDidSaveNotification:notification];
    ];

希望这会有所帮助!

【讨论】:

是的!你的博客非常好,让我了解上下文是如何工作的。我用你的建议编辑我的问题,你能看看我的代码吗?感谢您的良好回复:) 你的博客宕机了吗? @MycroftCanner 看起来他删除了他的博客,但你绝对应该阅读他在 objc.io 上的核心数据书——它真的很棒。

以上是关于带有 3 MOC 解决方案的 Ios CoreData(应用程序在保存过程中冻结)的主要内容,如果未能解决你的问题,请参考以下文章

保存托管对象上下文会在 iOS 5 的 performBlock 中创建死锁

由于验证规则上下文导致的 iOS 核心数据保存 MOC 问题

应用程序设计:处理带有背景上下文的核心数据;合并由 MOC 过滤的通知

解决VS+Qt不生成moc文件问题

nmake:添加带有推理规则的前缀?

如何通知主 MOC,后台 MOC 的变化