【问题标题】:How do I copy or move an NSManagedObject from one context to another?如何将 NSManagedObject 从一个上下文复制或移动到另一个上下文?
【发布时间】:2011-03-01 05:28:54
【问题描述】:

我有一个我认为是相当标准的设置,有一个从不保存的暂存器 MOC(包含从网上下载的一堆对象)和另一个永久的 MOC,它保留对象。当用户从 scratchMOC 中选择一个对象添加到她的库中时,我想要 1)从 scratchMOC 中删除该对象并插入到 PermanentMOC 中,或者 2)将该对象复制到 PermanentMOC 中。 Core Data FAQ 说我可以像这样复制一个对象:

NSManagedObjectID *objectID = [managedObject objectID];
NSManagedObject *copy = [context2 objectWithID:objectID];

(在这种情况下,context2 将是永久MOC。)但是,当我这样做时,复制的对象出错了;数据最初是未解决的。当它确实得到解决后,所有的值都为零;原始 managedObject 中的任何数据(属性或关系)都不会被实际复制或引用。因此,我看不出使用这个 objectWithID: 方法和使用 insertNewObjectForEntityForName: 将一个全新的对象插入到 PermanentMOC 之间有什么区别。

我意识到我可以在 PermanentMOC 中创建一个新对象并手动从旧对象复制每个键值对,但我对这个解决方案不太满意。 (我有许多不同的托管对象都有这个问题,所以我不想在继续开发时编写和更新所有这些对象的方法。)有更好的方法吗?

【问题讨论】:

    标签: cocoa cocoa-touch core-data nsmanagedobject nsmanagedobjectcontext


    【解决方案1】:

    斯威夫特 5

    如果你想知道如何在 2020 年复制 NSManagedObjects,下面的代码对我有用:

    // `Restaurant` is the name of my managed object subclass.
    
    // I like to have Xcode auto generate my subclasses (Codegen 
    //     set to "Class Definition") & then just extend them with 
    //     whatever functionality I need.
    
    extension Restaurant {
        public func copy() -> Restaurant? {
            let attributes = entity.attributesByName.map { $0.key }
            let dictionary = dictionaryWithValues(forKeys: attributes)
            guard let context = AppDelegate.shared?.persistentContainer.viewContext,
                let restaurantCopy = NSEntityDescription.insertNewObject(forEntityName: Restaurant.entityName, into: context) as? Restaurant
                else
            {
                return nil
            }
            restaurantCopy.setValuesForKeys(dictionary)
    
            return restaurantCopy
        }
    }
    

    【讨论】:

    • 关系在哪里?
    【解决方案2】:

    我自己也遇到了同样的问题,发现这篇关于创建断开连接的实体的文章以后可以添加到上下文中:http://locassa.com/temporary-storage-in-apples-coredata/

    这个想法是你有一个 NSManagedObject 因为你将在数据库中存储对象。我的障碍是这些对象中有许多是通过 HTTP API 下载的,我想在会话结束时丢弃其中的大部分。想想用户帖子流,我只想保存那些被收藏或保存为草稿的帖子。

    我所有的帖子都使用

    + (id)newPost {
        NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:self.managedObjectContext];
        Post *post = [[Post alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil];
        return post;
    }
    

    然后当它们被收藏时,它们被插入到本地托管对象上下文中

    + (BOOL)favoritePost:(Post *)post isFavorite:(BOOL)isFavorite
    {
        // Set the post's isFavorite flag
        post.isFavorite = [NSNumber numberWithBool:isFavorite];
    
        // If the post is being favorited and not yet in the local database, add it
        NSError *error;
        if (isFavorite && [self.managedObjectContext existingObjectWithID:post.objectID error:&error] == nil) {
            [self.managedObjectContext insertObject:post];
        }
        // Else if the post is being un-favorited and is in the local database, delete it
        else if (!isFavorite && [self.managedObjectContext existingObjectWithID:post.objectID error:&error] != nil) {
            [self.managedObjectContext deleteObject:post];
        }
    
        // If there was an error, output and return NO to indicate a failure
        if (error) {
            NSLog(@"error: %@", error);
            return NO;
        }
    
        return YES;
    }
    

    希望对您有所帮助。

    【讨论】:

    • 关于“insertIntoManagedObjectContext:nil”的注意事项:这似乎在 iOS8 中已被弃用。
    【解决方案3】:

    首先,在单个线程上拥有多个NSManagedObjectContext不是标准配置。 99% 的情况下,您只需要一个上下文,这将为您解决这种情况。

    为什么你觉得你需要不止一个NSManagedObjectContext

    更新

    这实际上是我见过的少数几个有意义的用例之一。为此,您需要将对象从一个上下文递归复制到另一个上下文。工作流程如下:

    1. 在持久上下文中创建新对象
    2. 从源对象中获取属性字典(使用-dictionaryWithValuesForKeys-[NSEntityDescription attributesByName] 来执行此操作。
    3. 将值字典设置到目标对象上(使用-setValuesForKeysWithDictionary
    4. 如果您有关系,则需要递归地执行此复制,并通过硬编码(以避免某些循环逻辑)或使用-[NSEntityDescription relationshipsByName] 来遍历关系

    正如另一位提到的,您可以从The Pragmatic Programmers Core Data Book 下载我的书中的示例代码,并查看解决此问题的方法。当然在书中我会更深入地讨论它:)

    【讨论】:

    • 我的应用有一个 mapView,它会在应用启动(或用户更改位置)时下载数百或数千个对象用作注释。我的想法是,将地图对象保存在永远不会保存的第二个 MOC(scratchMOC)中会更优雅、更高效;按照设计,只要应用程序启动,它就会被丢弃并重新加载。然后,用户可以选择一个或多个地图对象添加到他的库中,然后这些对象将被复制/移动到持久化 MOC。我认为这可以避免通过 MOC 迭代以在应用退出时删除数千个地图对象。
    • 由于某种原因,我无法获得正确的代码来执行第 2 步,所以这里是:NSDictionary *newValues = [oldObject dictionaryWithValuesForKeys:[[objectEntity attributesByName] allKeys]]
    • @Marcus - 您能否详细说明为什么不应该更喜欢在单个线程中使用多个 NSManagedObjectContext ?如果我们这样做,会有什么影响吗?谢谢!
    • @Marcus 现在对 CoreData 改进(如父子 MOC)仍然有效吗?因为现在在我看来,使用多个 MOC 不仅没问题,而且推荐使用,即使对于像创建新对象然后选择取消/保存这样的简单情况也是如此。 Apple 示例 CoreDataBooks 现在可以使用多个 MOC。
    • “单线程上的 NSManagedObjectContext 不是标准的”——完全不正确
    【解决方案4】:

    文档具有误导性且不完整。 objectID 方法本身并不复制对象,它们只是保证您已获得所需的特定对象。

    示例中的context2 实际上是源上下文而不是目标。你得到一个 nil 因为目标上下文没有具有该 ID 的对象。

    由于对象图的复杂性和上下文管理图的方式,复制托管对象相当复杂。您必须在新上下文中详细重新创建复制的对象。

    Here is some example code 是我从The Pragmatic Programmer's Core Data: Apple's API for Persisting Data on Mac OS X 的示例代码中截取的。 (您可能无需在 Pragmatic 网站上购买这本书就可以下载整个项目代码。)它应该让您大致了解如何在上下文之间复制对象。

    您可以创建一些复制对象的基本代码,但每个对象图关系的细节通常意味着您必须为每个数据模型进行自定义。

    【讨论】:

    • 我喜欢你的示例代码,但我不知道什么是“自我”。 copyObject 是要复制的对象。 toContext 目标上下文。 parentEntity 超类。但是什么是[self lookup] self 是什么类?这段代码在哪里?
    • 好的,我想我明白了。 lookup 只是一个字典,以避免在遍历对象图时重复相同的对象两次。
    • 嘿,首先,感谢sn-p,太棒了!当我在示例中使用此代码时(事件和设备模型,事件有 createDevice,设备有事件)我获取所有事件,然后调用你的函数,然后在新上下文中调用 save(),它会抛出一个异常,说 createDevice 是无...我做错了什么?谢谢!
    【解决方案5】:

    您需要确保保存 managedObject 所在的上下文。为了在不同的上下文中获取相同的对象,它需要存在于持久存储中。

    根据the documentationobjectWithID: 总是返回一个对象。因此,故障解决到一个对象的事实将所有nil 值都意味着它没有在持久存储中找到您的对象。

    【讨论】:

    • 刚刚确认这与宣传的一样——谢谢!然而,我首先想要第二个上下文(scratchMOC)的原因实际上是为了避免保存任何对象;我希望在用户退出时丢弃所有内容,并在应用程序启动时重新加载。猜猜这可能是不可能的。
    • 查看内存存储类型。它是一个只存在于内存中并在程序退出时消失的存储。请注意,您不能跨商店建立关系。
    • Alex,这看起来很诱人,但我很难弄清楚它是如何工作的。这两个存储(持久存储和内存存储)是在两个不同的上下文中还是在同一个上下文中?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多