具有按关系批量获取的多上下文 CoreData

Posted

技术标签:

【中文标题】具有按关系批量获取的多上下文 CoreData【英文标题】:Multi-Context CoreData with batch fetch by relationship 【发布时间】:2013-11-05 10:18:48 【问题描述】:

简单的问题

由于没有持久存储协调器的 NSManagedObjectContext 不支持 setFetchBatchSize 选择器,我使用了来自此 post 的解决方案,它适用于某些我想解决的问题。

这是数据库方案和Coredata结构,括号中的术语。测试应用程序有两个屏幕:带有聊天列表的主表和带有消息列表的详细表。主屏幕使用获取控制器中的 Main MOC 来显示表中的数据,并使用 Worker MOC 来创建聊天和消息。详情屏幕使用 Fetch MOC 来显示表格中的数据。

在我在主屏幕上创建一个带有消息的新聊天并在层次结构中的所有 MOC 上调用 save 来保存它们后,我无法通过选定的详细聊天屏幕获取消息。我在控制台中得到的只是:“CoreData:注释:总提取执行时间:0 行 0.0000 秒”。应用重启后可以获取这些数据。

这似乎与 Fetch MOC 中的错误消息有关,该错误消息与具有与我在 Main MOC 中的聊天不同的 objectID 的聊天有故障关系。因为当我在 Fetch MOC 中获取 Chat 对象,然后使用它来查找消息时,一切正常。

如果有人可以帮助我使用 Fetch MOC 解决此问题,或者使用我自己的 ID 字段而不是使用关系来获取所有对象图概念并获取数据,我将不胜感激。

一些代码

这是在 didFinishLaunchingWithOptions 上完成的 Coredata 堆栈初始化:

- (void)initializeCoreDataStack


    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"FaultsFetching" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];

    _writerMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_writerMOC setUndoManager:nil];
    [_writerMOC setPersistentStoreCoordinator:_persistentStoreCoordinator];

    _mainThreadMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_mainThreadMOC setUndoManager:nil];
    [_mainThreadMOC setParentContext:_writerMOC];

    _fetchMainThreadMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_fetchMainThreadMOC setUndoManager:nil];
    [_fetchMainThreadMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    [_fetchMainThreadMOC setPersistentStoreCoordinator:_persistentStoreCoordinator];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:_writerMOC];

    NSURL *storeURL = [APP_DOC_DIR URLByAppendingPathComponent:@"FaultsFetching.sqlite"];
    NSError *error = nil;
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    


- (void)backgroundContextDidSave:(NSNotification *)notification

    [_fetchMainThreadMOC mergeChangesFromContextDidSaveNotification:notification];
    NSLog(@"Yep, everything is merged");

这是我创建 Worker MOC 的方法:

+ (NSManagedObjectContext *)createPrivateMOC

    CoreDataManager *scope = [CoreDataManager sharedInstance];

    NSManagedObjectContext *workerMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    workerMOC.parentContext = scope.mainThreadMOC;
    [workerMOC setUndoManager:nil];
    workerMOC.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    return workerMOC;

这是多上下文保存的样子。参数 async 是 YES。自然地,这个选择器在工作者 MOC 的 performBlock 选择器中被调用

+ (void)writeToDiskAsync:(BOOL)async

    CoreDataManager *scope = [CoreDataManager sharedInstance];

    NSManagedObjectContext *writeManagedObjectContext = scope.writerMOC;
    NSManagedObjectContext *mainManagedObjectContext = scope.mainThreadMOC;

    PerformBlock mainMOCBlock = ^
    
        NSError *mainError = nil;
        if ([mainManagedObjectContext hasChanges] && ![mainManagedObjectContext save:&mainError])
        
            ALog(@"Unresolved error %@, %@", mainError, [mainError userInfo]);
        

        PerformBlock writerBlock = ^
        
            NSError *writeError = nil;
            if ([writeManagedObjectContext hasChanges] && ![writeManagedObjectContext save:&writeError])
            
                ALog(@"Unresolved error %@, %@", writeError, [writeError userInfo]);
            
            NSLog(@"Yep, everything is saved");
        ;
        [scope performBlock:writerBlock onMOC:writeManagedObjectContext async:async];
    ;
    [scope performBlock:mainMOCBlock onMOC:mainManagedObjectContext async:async];


- (void)performBlock:(PerformBlock)block onMOC:(NSManagedObjectContext *)target async:(BOOL)async

    if (async)
        [target performBlock:block];
    else
        [target performBlockAndWait:block];

这是我在详细屏幕上的获取结果控制器,其中“detailItem”是从主屏幕设置的聊天实体,“[CoreDataManager sharedInstance]”是单例:

- (NSFetchedResultsController *)fetchedResultsController

    if (_fetchedResultsController != nil) 
        return _fetchedResultsController;
    
    if (self.detailItem == nil)
        return nil;

    NSManagedObjectContext *fetchMOC = [CoreDataManager sharedInstance].fetchMainThreadMOC;

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Messages" inManagedObjectContext:fetchMOC];
    [fetchRequest setEntity:entity];

    [fetchRequest setFetchBatchSize:20];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sentDate" ascending:NO];

    [fetchRequest setSortDescriptors:@[sortDescriptor]];

    NSPredicate *chatPredicate = [NSPredicate predicateWithFormat:@"relatedChat=%@", self.detailItem.objectID];
    [fetchRequest setPredicate:chatPredicate];

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:fetchMOC sectionNameKeyPath:@"sectionIdentifier" cacheName:nil];
    _fetchedResultsController.delegate = self;

    NSError *error = nil;
    if (![_fetchedResultsController performFetch:&error]) 
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    

    return _fetchedResultsController;

一点背景

父/子 MOC 用于提高从一开始就没有正确编写的应用程序的稳定性和响应能力。但是,由于现在与 Coredata 相关的一切都或多或少是中心化的,因此可以将堆栈更改为不同的东西。 SectionIdentifier 用于按天对消息进行分组,如下所示:http://i.imgur.com/17tuKS7.png 稍后我可能会添加其他内容,也很抱歉链接和图片:声誉和愚蠢的东西

【问题讨论】:

你在线程中使用了任何worker MOC吗? 我在主屏幕上的 IBAction 选择器的按钮触摸上使用工人 MOC 来创建聊天及其消息。我在工人 MOC 上使用 performBlock 选择器 我可以看到你在同一个线程中创建所有的 MOC,所以我想知道你是否在其他线程中使用它们中的任何一个,因为如果你是,你会在保存和获取时遇到问题。 这应该不是问题,因为我将所有数据保存在一系列 performBlock 调用中,我会将此代码添加到问题中。 会不会和这个问题有关? ***.com/questions/11990279/…。如果在保存新插入的对象之前调用 gainPermanentIDsForObjects: 会有所不同吗? 【参考方案1】:

这是由于一个错误。解决方法是在保存新插入的对象之前调用obtainPermanentIDsForObjects:

有关详细信息,请参阅以下 SO 问题:

Core Data: Do child contexts ever get permanent objectIDs for newly inserted objects?

【讨论】:

我很好奇这个 gainPermanentIDsForObjects: 是否是一个昂贵的操作,我是否应该为所有插入的对象调用它 听起来只有在某些情况下才需要在子上下文中保存插入的对象之前。它确实需要与商店通信,因此会在主线程上引起一些循环。我会在您遇到问题的情况下使用它。

以上是关于具有按关系批量获取的多上下文 CoreData的主要内容,如果未能解决你的问题,请参考以下文章

CoreData 和一对多关系的并发错误

没有上下文的CoreData关系设置?

CoreData 关系延迟加载?

没有上下文的CoreData关系设置?

Core Data - 批量处理获取结果的属性

Coredata 并发问题