iOS - 使用 MagicalRecord 管理两个 CoreData 模型

iOS - Managing two CoreData models with MagicalRecord

我正在使用 MagicalRecord 来处理 CoreData 模型,该模型可能会在将来进行版本控制。

现在我需要在我的应用程序中添加一个预填充的数据库,其中一个实体包含大约 80000 个对象;此数据是静态的,我预计它不会改变。

如果我将这个实体添加到现有模型中,每次模型更改时我都需要生成一个新的种子数据库,从而增加项目的复杂性。

更好的解决方案是为新实体创建第二个模型:种子数据库永远不会改变,第一个模型可以负责其版本控制,而不管新模型如何。不需要两个模型之间的关系。

在现有模型的基础上,我也在使用 RestKit,这是所有设置的方式:

[MagicalRecord setupAutoMigratingCoreDataStack];
RKManagedObjectStore *managedObjectStore =
    [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:
        [NSPersistentStoreCoordinator MR_newPersistentStoreCoordinator]];
self.objectManager.managedObjectStore = managedObjectStore;
[managedObjectStore createManagedObjectContexts];
// bind RK with MagicalRecord
[NSManagedObjectContext MR_setRootSavingContext:
    managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:
    managedObjectStore.mainQueueManagedObjectContext];
managedObjectStore.managedObjectCache = [[RKFetchRequestManagedObjectCache alloc] init];

新模型将不会与 RestKit 一起使用。

这对 MagicalRecord 可行吗? 我已经阅读了它的文档,但能找到任何有用的东西。

非常感谢, 丹

更新

让我们使用 xcode 编辑器创建一个包含 4 个实体(Foo、Bar、Blarg、Baz)的数据库模型。 模型编辑器有一个无法删除的默认配置,所以我们只能添加两个新配置(SeedConfiguration 和 UserConfiguration),将 Foo 添加到第一个,将其他三个添加到第二个。 这两个配置应该保存在seed.sqlite和user.sqlite中。 在这一点上,我想要 运行 一个脚本,它用数千个 Foo 对象填充 seed.sqlite :一旦生成,这个文件将被放入项目资源中,并在启动时复制到应用程序目录中; user.sqlite 将在 运行 时生成并用于管理用户信息。

当我以 "script" 模式启动应用程序以填充 seed.sqlite 时,正确创建了两个 sqlite 文件,但它们都包含所有实体,而我希望在 seed.sqlite 和 user.sqlite.

中的 Bar、Blarg、Baz

即使它包含所有其他(空)实体,我是否应该插入 Foo 对象并复制结果 seed.sqlite?

以下是如何在一个协调器中创建两个持久性存储:

为了澄清起见,如果我可以只有一个 sqlite 文件就好了,但是这样做我必须在每次模型更改时生成种子数据库。

我不想回答太长,因为我不使用 MagicalRecord,而且我不知道它如何管理模型配置。

也就是说,您想要解决此问题的方法是使用模型配置和多个存储文件。这个问题既容易理解又有据可查。

Apple's documentation is a good starting point, and there are numerous articles and examples on the web.

编辑

好的 DAN,这是一个使用多个配置的有点人为(但简单)的示例。您应该能够 copy/paste 将其放入测试文件并 运行 它,这应该允许您跟踪正在发生的事情并获得基本的理解。

请注意,这不是我建议编写生产代码或测试的方式(我也不建议忽略错误),但我希望这有助于解释一些事情并允许您进行实验。

我将代码分成几个辅助方法,希望能更好地解释。

首先,让我们创建一个简单的模型,其中包含四个实体,我们将在每个配置中放入两个实体。

- (NSManagedObjectModel *)makeConfigurationModel {
    NSAttributeDescription *nameAttr = [[NSAttributeDescription alloc] init];
    nameAttr.name = @"name";
    nameAttr.attributeType = NSStringAttributeType;

    NSEntityDescription *foo = [[NSEntityDescription alloc] init];
    foo.name = @"Foo";
    foo.properties = @[[nameAttr copy]];
    NSEntityDescription *bar = [[NSEntityDescription alloc] init];
    bar.name = @"Bar";
    bar.properties = @[[nameAttr copy]];

    NSEntityDescription *blarg = [[NSEntityDescription alloc] init];
    blarg.name = @"Blarg";
    blarg.properties = @[[nameAttr copy]];
    NSEntityDescription *baz = [[NSEntityDescription alloc] init];
    baz.name = @"Baz";
    baz.properties = @[[nameAttr copy]];


    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
    model.entities = @[foo, bar, blarg, baz];
    [model setEntities:@[foo, bar] forConfiguration:@"One"];
    [model setEntities:@[blarg, baz] forConfiguration:@"Two"];

    return model;
}

接下来,将两个商店分配给 PSC 并创建一些示例实体的函数。此功能还检查以确保可以访问所有实体。

- (void)setupDatabaseWithModel:(NSManagedObjectModel*)model
                        store1:(NSURL*)store1URL
                        store2:(NSURL*)store2URL {
    @autoreleasepool {
        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
            initWithManagedObjectModel:model];
        [psc addPersistentStoreWithType:NSSQLiteStoreType
                          configuration:@"One"
                                    URL:store1URL
                                options:nil
                                  error:NULL];
        [psc addPersistentStoreWithType:NSSQLiteStoreType
                          configuration:@"Two"
                                    URL:store2URL
                                options:nil
                                  error:NULL];
        NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
            initWithConcurrencyType:NSMainQueueConcurrencyType];
        moc.persistentStoreCoordinator = psc;

        // Add some entities...
        NSArray *entityNames = @[@"Foo", @"Bar", @"Blarg", @"Baz"];
        for (NSString *e in entityNames) {
            NSManagedObject *obj =
                [NSEntityDescription insertNewObjectForEntityForName:e
                                              inManagedObjectContext:moc];
            [obj setValue:[NSString stringWithFormat:@"%@ 1", e] forKey:@"name"];
        }
        [moc save:NULL];

        // Should have all of them in this MOC...
        for (NSString *e in entityNames) {
            NSFetchRequest *fetchRequest = [NSFetchRequest
                fetchRequestWithEntityName:e];
            NSArray *result = [moc executeFetchRequest:fetchRequest error:NULL];
            XCTAssertEqual(1, result.count);
            NSManagedObject *obj = [result firstObject];
            XCTAssertEqualObjects(([NSString stringWithFormat:@"%@ 1", e]),
                                  [obj valueForKey:@"name"]);
        }
    }
}

以及检查某些实体是否在商店中的功能。

- (void)checkStore:(NSURL*)storeURL
             model:(NSManagedObjectModel*)model
           present:(NSArray*)present
        notPresent:(NSArray*)notPresent {
    @autoreleasepool {
        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
            initWithManagedObjectModel:model];
        [psc addPersistentStoreWithType:NSSQLiteStoreType
                          configuration:nil
                                    URL:storeURL
                                options:nil
                                  error:NULL];
        NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
            initWithConcurrencyType:NSMainQueueConcurrencyType];
        moc.persistentStoreCoordinator = psc;

        for (NSString *e in present) {
            NSFetchRequest *fetchRequest = [NSFetchRequest
                fetchRequestWithEntityName:e];
            NSArray *result = [moc executeFetchRequest:fetchRequest error:NULL];
            XCTAssertEqual(1, result.count);
            NSManagedObject *obj = [result firstObject];
            XCTAssertEqualObjects(([NSString stringWithFormat:@"%@ 1", e]),
                                  [obj valueForKey:@"name"]);
        }
        for (NSString *e in notPresent) {
            NSFetchRequest *fetchRequest = [NSFetchRequest
                fetchRequestWithEntityName:e];
            NSArray *result = [moc executeFetchRequest:fetchRequest error:NULL];
            XCTAssertEqual(0, result.count);
        }
    }
}

还有一个删除 URL

的小帮手
static void removeURL(NSURL ** url) {
    [[NSFileManager defaultManager] removeItemAtURL:*url error:NULL];
}

还有一个测试函数...

- (void)testConfigurations {
    __attribute__((cleanup(removeURL))) NSURL * __autoreleasing dirURL =
        [[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
                                                inDomain:NSUserDomainMask
                                       appropriateForURL:nil
                                                  create:YES
                                                    error:NULL]
            URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    [[NSFileManager defaultManager] createDirectoryAtURL:dirURL
                             withIntermediateDirectories:YES
                                              attributes:nil
                                                   error:NULL];

    NSManagedObjectModel *model = [self makeConfigurationModel];
    NSURL *store1URL = [dirURL URLByAppendingPathComponent:@"store1"];
    NSURL *store2URL = [dirURL URLByAppendingPathComponent:@"store2"];
    [self setupDatabaseWithModel:model store1:store1URL store2:store2URL];
    [self checkStore:store1URL
               model:model
             present:@[@"Foo", @"Bar"]
          notPresent:@[@"Blarg", @"Baz"]];
    [self checkStore:store2URL
               model:model
             present:@[@"Blarg", @"Baz"]
          notPresent:@[@"Foo", @"Bar"]];
}