在 Core Data 存储之间切换

Posted

技术标签:

【中文标题】在 Core Data 存储之间切换【英文标题】:Switching between Core Data stores 【发布时间】:2012-10-03 01:00:00 【问题描述】:

我目前正在更新应用程序以使用 Core Data。您可以说的应用程序是“数据库查看器”,一次只能查看一个数据库。每个数据库都保存在自己单独的文件夹中。目前,数据被下载并存储为一组 plist 文件。

在新版本中,我需要将这些 plist 数据库转换为 Core Data 存储(每个数据库一个存储。)我已经设置了创建单独存储文件和创建实体的方法。问题是所有实体都保存到我创建的第一个数据库中,而不是保存到“当前”或“最后创建”文件中。

我使用的基本流程是:

//For each database 
//Create the sqlite file and set up NSManagedObjectContext
[MagicalRecord setupCoreDataStackWithStoreNamed:
    [NSURL fileURLWithPath:
    [NSString stringWithFormat:@"%@/%@/%@.sqlite",
    dirPath, directory, directory]]];
NSManagedObjectContext *managedObjectContext = 
    [NSManagedObjectContext MR_contextForCurrentThread];

//Iterate through all the plist files and create the necessary entities.
//Save new entities to file
[managedObjectContext MR_save];
//Clean up all cashes
[MagicalRecord cleanUp];

如何在商店之间正确切换,本质上是在每次切换之间“重置”所有内容。最好(如果可能的话)使用魔法记录。

编辑: 我发现了问题的一部分,并删除了大部分不需要的行为。事实证明,您不能在后台线程上可靠地调用[MagicalRecord cleanUp]。此外,它没有做我认为应该做的事情(见下文)。我最终在每次“保存”后回调主线程以重置核心数据堆栈。这样做会为前三个数据库创建一个新的上下文。之后,它从三个数据库前的数据库中复制上下文。所以在一个循环中使用了相同的三个上下文。

这是我目前拥有的; 我通过创建一个后台线程来启动该过程并运行代码以在后台创建单个数据库:

backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
    dispatch_async(backgroundQueue, ^(void) 
        [self createSQLiteDatabase:updateList];
    );

然后创建堆栈和数据库:

- (void)createSQLiteDatabase:(NSArray *)updateList

    NSString *directory = [updateList objectAtIndex:0];
    [MagicalRecord setupCoreDataStackWithStoreNamed:
        [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@/%@.sqlite",
        dirPath, directory, directory]]];
    NSManagedObjectContext *managedObjectContext = 
        [NSManagedObjectContext MR_contextForCurrentThread];
    //Check to see if the stack has reset
    NSLog(@"Before:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);

    //Create and add entities to context...

    //Prepare for next loop
    NSLog(@"After:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);
    [managedObjectContext MR_saveNestedContexts];
    [NSManagedObjectContext MR_resetContextForCurrentThread];

    NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:updateList];
    [temp removeObjectAtIndex:0];

    dispatch_async(dispatch_get_main_queue(), ^(void)
        [self shouldContinueUpdating:temp];
    );

然后重置所有内容并重复所有数据库:

- (void)shouldContinueUpdating:(NSArray *)databases

    //preform cleanup on main thread and release background thread
    [MagicalRecord cleanUp];
    dispatch_release(backgroundQueue);

    if ([databases count] != 0) 
        backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
        dispatch_async(backgroundQueue, ^(void) 
            [self createSQLiteDatabase:databases];
        );
    

使用两个 NSLog,我在控制台中得到了这个:(使用六个数据库,无论我转换多少个数据库,模式都是相同的。)

//First Loop
Before:0
After:308
//Second Loop
Before:0
After:257
//Third Loop
Before:0
After:37
//Fourth Loop
Before:308 
After:541 
//Fifth Loop
Before:257
After:490
//Sixth Loop
Before:37
After:270
... Keep adding to each of the three contexts.

[MagicalRecord cleanUp] 并没有按照它所说的那样做。这是该方法应该做的事情。

+ (void) cleanUpStack;

[NSManagedObjectContext MR_cleanUp];
[NSManagedObjectModel MR_setDefaultManagedObjectModel:nil];
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:nil];
[NSPersistentStore MR_setDefaultPersistentStore:nil];

但事实证明,每次我保存时,NSStoreCoordinator 都是同一个协调器,在同一个内存位置,并且每个商店都在闲逛。有些东西不正常...

【问题讨论】:

我无法理解您为什么需要 CoreData。从 plists 创建 NSDicationaries 并使用 NSDictionary 来提供数据。 CoreData 可以满足您的需求,但需要大量额外的工作,最终不会真正提供任何好处。 我做了一个示例,我计算了在示例数据库中排序和显示一组实体所需的时间。加载和排序我从 plist 中获得的 NSArray 时,Core Data 的速度要快得多。此外,CD 兑现数据的方式还减少了样本的内存占用。 在这种情况下,您能否创建一个名为 Database 之类的实体,然后在这些实体之间切换而不是在商店之间切换?这应该会让你的事情变得更容易。 这是我开始做的,但通过我的测试意识到,如果我想找到所有对象:“someEntity”,它会搜索所有数据库,如果我有多个在一家商店。我可以添加一个谓词来获取属于“数据库 A”的谓词,但是为什么要搜索所有数据库呢?这只会减慢应用程序的速度,尤其是因为我可以在一个数据库中处理数千个实体。 "但是我为什么要搜索所有的数据库呢?" - 如果您的关系设置正确,您真的不必这样做。 myDatabase.entityAObjects 将返回属于 myDatabase 的 EntityA 对象。没有获取也没有谓词。当然,在不知道架构的情况下,我无法给出更好的答案(在您的情况下,这个答案可能不正确),但这就是我的处理方式。 【参考方案1】:

MagicalRecord 可能不是您完成这项工作的最佳工具...

首先,让我们更正您对 setupCoreDataStackWithStoreNamed: 方法的使用。该参数采用 NSString,不是 URL,也不是文件路径。 MagicalRecord 将为您选择正确的路径并在那里创建您的商店。您生成的 sqlite 文件可能会以您想要的路径命名。

接下来,您需要为此文件动态创建 CoreData 模型。这有点困难,但有可能。您需要遍历这些 plist 文件,解释实体、属性和关系,并创建相应的 NSEntityDescriptions、NSAttributeDescriptions 和 NSRelationshipDesctiptions 并“手动”填充 NSManagedObjectModel。你要找方法

- [NSManagedObjectModel setEntities:(NSArray *)]

以及NSEntityDescription、NSAttributeDescription和NSRelationshipDescription的创建方法。

您还需要将此模型保存在某个地方,这样您就不必每次都重新创建它。幸运的是,它符合 NSCoding,所以您应该能够将其保存到磁盘。

之后,您可能需要填充数据。从这里,MagicalRecord 可以为您提供帮助。我建议查看我为Cocoa is My Girlfriend 写的Importing Data Made Easy 博客文章

如果你想“切换存储”,我猜这意味着你想为你拥有的每个 plist 文件创建一个新的存储,那么你将不得不为每个文件拆除整个核心数据堆栈.如果您设法在此项目中使用 MagicalRecord,则需要查看 [MagicalRecord cleanUp],然后重新开始。如果每个模型都相同,您可以通过发布 Persistent Store Coordinator 并为您的商店创建一个新协调器来解决问题。但由于您的“模式”可能会有所不同,您只想从头开始一切并重新开始。

【讨论】:

setupCoreDataStackWithStoreNamed:查看输入的类,如果是 URL,它会在该 URL 处打开/创建数据库。如果您使用字符串,它只能在文档文件夹的根目录中找到数据库,而不是在子文件夹中。我可以创建 sqlite 文件,并很好地创建实体。我遇到的问题是换店。看着 [MagicalRecord cleanUp];它应该拆除整个核心数据堆栈。但是有些东西仍然存在,因此使用的是“旧”存储文件,而不是我刚刚创建的新文件。如果我只需要为一个数据库创建一个 sql 文件,完美... multiple=fail【参考方案2】:

原来我遇到的问题是因为 MagicalRecord 中的一个错误。我在这里提交了一个 git 问题:https://github.com/magicalpanda/MagicalRecord/issues/270

【讨论】:

以上是关于在 Core Data 存储之间切换的主要内容,如果未能解决你的问题,请参考以下文章

如何观察 Core Data 持久存储在包含应用程序和扩展程序之间的变化

iPhone 和 Apple Watch 应用之间的 Core Data SQLite 存储更改通知

当应用程序从 4.0 中的任务列表中删除时,Core Data 存储消失

使用存储的 JSON 而不是 Core Data

将 NSNumber* 的 NSArray 存储为 Core Data 中的 NSString

如何在 Core Data 中存储一组自定义(和重复)对象?