是否立即保存文档NSManagedObjectContext?

ipm*_*mcc 4 core-data core-data-migration

从10.7上标准的基于Xcode文档的应用程序w/CoreData模板开始,我遇到了一些令人沮丧的行为.我确信这是一件很简单的事情.

让我们说在我的NSPersistentDocument子类中,我有这样的东西,连接到窗口中的一个按钮:

- (IBAction)doStuff:(id)sender
{        
    NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: @"MyEntity"];
    NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
    [self.managedObjectContext save: NULL];
}
Run Code Online (Sandbox Code Playgroud)

如果我创建一个新文档并单击该按钮,我将收到以下错误:This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.我得到了这个.我们还没有保存,没有持久的商店.说得通.

现在让我说我把它分成两个动作,连接到不同的按钮,如下:

- (IBAction)doStuff:(id)sender
{        
    NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: @"MyEntity"];
    NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
}

- (IBAction)doOtherStuff:(id)sender
{        
    [self.managedObjectContext save: NULL];
}
Run Code Online (Sandbox Code Playgroud)

如果我创建一个新文档并按下第一个按钮,那么在按下该按钮后的某个不确定时间(弄脏文档),自动保存将自动保存并自动保存文档,这将在临时位置创建一个商店.如果我按下第二个按钮,则没有投诉(因为现在有一个商店.)

我需要我的文档能够从一开始就进行managedObjectContext保存.我在后台线程上踢了一些东西,我需要后台上下文的保存操作(和通知),以便将后台线程所做的更改合并到主线程的managedObjectContext中.

我想过尝试强制自动保存,但自动保存过程看起来完全异步,所以我必须跳过箍来禁用任何可能导致managedObjectContext保存的UI交互,直到第一次自动保存操作完成.

我还考虑创建一个内存存储来弥补创建新文档和第一次自动保存之间的差距,但是我不清楚如何将内存存储区中的内容迁移到磁盘存储区并同步删除内存存储区.首次自动保存操作.

任何人对我如何处理这个有任何想法?

ipm*_*mcc 7

所以我愚弄了一段时间,包括尝试@ Aderstedt的建议.这种做法没有奏效,因为伪造的通知似乎只是告诉接收上下文"哎,有持久性存储检查,我已经更新了他们!",而实际上,我没有,因为根本不存在.我最终找到了一种有效的方法.不幸的是,它依赖于Lion-only功能,所以我仍然在寻找一种不需要Lion的方法.

背景

我想使用NSPersistentDocument方法.虽然我没有在任何地方明确地找到这个文档,但我发现了一些论坛帖子,并且经历了一系列经验证据,你无法调用-[NSManagedObjectContext save:]属于NSPersistentDocument的上下文.如问题中所述,如果您在文档保存之前调用它,它将没有存储,因此保存将失败.即使在商店存在之后,通过直接保存上下文(而不是通过文档保存API),您实际上正在更改NSPersistentDocument后面的磁盘表示,并且您将获得文档弹出表,其中说明:

文件已被其他应用程序修改

简而言之,NSPersistentDocument期望控制关联的NSManagedObjectContext本身的保存操作.

另外值得一提的是:这里的目标是确保UI使用的上下文不会触发(或至少是最小的)I/O以保持响应.我最终确定的模式是有3个上下文.NSPersistentDocument拥有的一个上下文,它负责与文档一起执行文件I/O. 用于绑定UI的第二个有效只读的上下文.(我意识到很多人都想要改变模型的UI,所以这对他们来说可能不那么令人兴奋,但对我来说并不是必需的.)以及在后台线程上使用的第三个上下文,它从Web上异步加载数据服务,并希望将其推送到其他上下文,以便它可以保存在磁盘上并在UI中显示,而不会阻止网络I/O上的UI.

仅狮子解决方案

Lion的CoreData实现中的新父/子NSManagedObjectContext功能非常适用于此.我用一个新的并发类型为NSPrivateQueueConcurrencyType的MOC替换了NSPersistentDocument的NSManagedObjectContext.这将是"根"上下文.然后我使用NSMainQueueConcurrencyType并发创建了UI上下文,并使其成为根上下文的子项.最后,我将网络加载上下文设置为NSPrivateQueueConcurrencyType上下文,该上下文是UI上下文的子项.这种方式的工作方式是我们在后台启动网络加载操作,它会更新网络上下文.完成后,它会保存上下文.与父/子关系,节省了孩子方面推动改变成父上下文(UI方面),但并没有保存到存储父上下文.在我的例子中,我还从网络上下文中侦听NSManagedObjectContextDidSaveNotification通知,然后告诉它的父节点也要保存(这会将更改从UI上下文推送到根/磁盘上下文,但不会将其保存到磁盘.)

在这一系列事件结束时,所有上下文都是一致的,我们仍然没有强制实际保存底层根上下文,因此我们没有在管理磁盘的角色中违反NSPersistentDocument表示.

一个问题是,如果你想阻止子上下文的保存生成撤销(即这是一个网络加载操作,没有什么可以撤消)你必须在每个父上下文上禁用UndoRegistration,同时在链上传播更改.

前狮子的努力

我真的很想为这个问题找到一个与Lion兼容的解决方案.在放弃之前,我尝试了一些事情.我第一次尝试在内存中的存储与文件上初始化的PSC相关联,这样我就可以做的NSManagedObjectContext保存文档保存之前,然后再迁移在内存存储上的第一次保存.那部分很有效.但是一旦存在磁盘存储,这种方法就是假的,因为在它保存到磁盘之后我们遇到了同样的问题,即NSPersistentDocument拥有的与PSC连接的MOC的任何保存必须由文档完成.

我还试图破解一种机制,使用NSManagedObjectContextObjectsDidChangeNotification有效负载将更改从一个上下文移动到另一个上下文.虽然我能够实现这一点(对于"工作"的一些名义定义),但我发现这种方法即将出现大问题.具体来说,一次迁移这些更改很容易,但如果在保存操作之前再次更改会怎样?然后,我将无法维护源上下文中的OID到目标上下文中的OID的长期映射.这很快就变丑了.如果有人有兴趣,这就是我想出的:

@interface NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification;
@end

@implementation NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)

- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification
{
    if (![NSManagedObjectContextObjectsDidChangeNotification isEqual: notification.name])
        return;

    if (notification.object == self)
        return;

    NSManagedObjectContext* sourceContext = (NSManagedObjectContext*)notification.object;

    NSAssert(self.persistentStoreCoordinator == sourceContext.persistentStoreCoordinator, @"Can't merge changes between MOCs with different persistent store coordinators.");

    [sourceContext lock];

    // Create object in the local context to correspond to inserted objects...
    NSMutableDictionary* foreignOIDsToLocalOIDs = [NSMutableDictionary dictionary];
    for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSInsertedObjectsKey])
    {
        NSManagedObjectID* foreignOID = foreignMO.objectID;
        NSManagedObject* localMO = [[[NSManagedObject alloc] initWithEntity: foreignMO.entity insertIntoManagedObjectContext: self] autorelease];
        [foreignOIDsToLocalOIDs setObject: localMO.objectID forKey: foreignOID];
    }

    // Bring over all the attributes and relationships...
    NSMutableSet* insertedOrUpdated = [NSMutableSet set];
    [insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSInsertedObjectsKey]];
    [insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSUpdatedObjectsKey]];

    for (NSManagedObject* foreignMO in insertedOrUpdated)
    {
        NSManagedObjectID* foreignOID = foreignMO.objectID;
        NSManagedObjectID* localOID = [foreignOIDsToLocalOIDs objectForKey: foreignOID];
        localOID = localOID ? localOID : foreignOID;
        NSManagedObject* localMO = [self objectWithID: localOID];

        // Do the attributes.
        [localMO setValuesForKeysWithDictionary: [foreignMO dictionaryWithValuesForKeys: [[foreignMO.entity attributesByName] allKeys]]];

        // Do the relationships.
        NSDictionary* rByName = foreignMO.entity.relationshipsByName;
        for (NSString* key in [rByName allKeys])
        {
            NSRelationshipDescription* desc = [rByName objectForKey: key];
            if (!desc.isToMany)
            {
                NSManagedObject* relatedForeignMO = [foreignMO valueForKey: key];
                NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
                NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
                relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
                NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
                [localMO setValue: localRelatedMO forKey: key];
            }
            else
            {
                id collection = [foreignMO valueForKey: key];
                id newCollection = [NSMutableSet set];
                if ([collection isKindOfClass: [NSOrderedSet class]])
                {
                    newCollection = [NSOrderedSet orderedSet];
                }

                for (NSManagedObject* relatedForeignMO in collection)
                {
                    NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
                    NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
                    relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
                    NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
                    [newCollection addObject: localRelatedMO];
                }
                [localMO setValue: newCollection forKey: key];
            }
        }
    }

    // And delete any objects which pre-existed in my context.
    for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSDeletedObjectsKey])
    {
        NSManagedObjectID* foreignOID = foreignMO.objectID;
        NSManagedObject* localMO = [self existingObjectWithID: foreignOID error: NULL];
        if (localMO)
        {
            [self deleteObject: localMO];
        }
    }

    [sourceContext unlock];
}

@end
Run Code Online (Sandbox Code Playgroud)

结论

在并发管理和这个父/子功能的改进之间,我很快就失去了追求前狮子解决方案的兴趣.我开始认为,狮子会之前的解决方案实际上是"不要使用NSPersistentDocument".我可以说,如果我放弃这个要求,所有这些痛点都会消失.没有它,你可以随时保存上下文并迁移商店,但自然你必须自己完成所有工作.