+ [NSManagedObjectModel mergedModelFromBundles :: forStoreMetadata:]始终返回nil

时间:2019-04-06 02:44:11

标签: ios cocoa core-data

我有一个包含15个版本的Core Data模型。它具有将代码从当前商店的版本逐步迁移到发布时的最新版本的代码。

关键在于调用

    NSDictionary* options = @{ NSMigratePersistentStoresAutomaticallyOption : @true,
                               NSInferMappingModelAutomaticallyOption : @true };
    NSDictionary* sourceMetadata = [NSPersistentStoreCoordinator
                                        metadataForPersistentStoreOfType: inType
                                        URL: inSourceStore
                                        options: options
                                        error: outError];
    NSManagedObjectModel* model = [NSManagedObjectModel mergedModelFromBundles: @[ [NSBundle bundleForClass: [self class]] ]
                                                        forStoreMetadata: inSourceMetadata];

但这总是返回nil,我不确定为什么。现有商店的版本为14,新模型的版本为15。

现在,对模型的最后更改是相当琐碎的(添加了两个可选字段),所以我本以为它可以自动推断映射,但这是行不通的,因此我从版本14到版本15使用Xcode的助手,并且没有进行任何更改。

有什么想法为什么返回零,或者我可以做进一步的调查吗?

同样,当我说“版本14”时,我指的是.xcdatamodel文件的顺序编号。有什么方法可以查看实际存储并确定Core Data认为的模型版本吗?

2 个答案:

答案 0 :(得分:1)

首先,您似乎知道自己在做什么,并且经过14项Core Data迁移以及所有这些工作,都得以幸存。因此,我认为您应该警惕一些愚蠢的额头拍击式错误。

确保[NSBundle bundleForClass: [self class]]返回期望的捆绑软件,该捆绑软件包含目录Contents/Resources/YourModelName.momd,并且该目录包含所有必需的.mom文件(每个版本一个),以及一个VersionInfo.plist个文件。我的版本还包含一个.omo文件,仅用于最新版本。

现在我将回答您的第二个问题,这确实可以帮助您回答第一个问题。

在该VersionInfo.plist文件中,您将找到一个名为NSManagedObjectModel_VersionHashes的词典,该词典又包含子词典,每个版本一个键。每个版本子词典都包含每个实体名称和值的键,该键是该版本中该实体的属性和关系的32字节(256位)哈希值。我们称这些为模型散列

现在使用SQLite查看器或sqlite3命令行工具打开商店数据库文件。在该数据库中,除了模型中每个实体的一个表之外,您还将看到一个名为Z_METADATA的表,其中包含一行和三列。名为Z_PLIST的列的值被键入为二进制数据的blob。将该数据复制到文件中,以扩展名.plist命名,双击并惊讶地在您喜欢的plist编辑器中将其打开,因为该数据实际上是代表XML格式的Apple属性列表的文本字符串。实际上,其键NSStoreModelVersionHashes的值是一个子词典,就像VersionInfo.plist文件中的子词典一样。我们称其为商店散列。 32字节(256位)的版本哈希是Base64编码的。 (有44个Base64字符。由于每个Base64字符代表6位,所以44个字符最多可以代表44 * 6 = 264位。)

最后,要回答第二个问题,传递给storeMetadata的{​​{1}}实际上是商店中的+[NSManagedObjectModel mergedModelFromBundles:forStoreMetadata:],其中包含那些商店哈希值Z_METADATA将传入的+[NSManagedObjectModel mergedModelFromBundles:forStoreMetadata:]中每个候选数据模型的这些存储散列模型散列进行比较,并返回其模型散列与所有实体的商店散列匹配,并且在两侧没有多余的不匹配实体。

因此,您会发现手动进行比较比较麻烦。但是,也许在搜寻这些塑料桩时,您会发现那个额头拍手。如果没有,给我们更多关于您粘贴的代码的上下文,也许有人可以提供帮助。

答案 1 :(得分:0)

啊。事实证明,我编辑了最新的模型版本,而不是添加新的模型版本。这就是为什么没有匹配的原因。恢复了最新版本并添加具有更改的新模型版本后,即使没有默认的映射模型,它也可以正常工作。

我通过使用以下方法测试每个模型以查看其是否与源元数据相匹配来解决这个问题:

    NSDictionary* storeHashes = [sourceMetadata objectForKey: NSStoreModelVersionHashesKey];
    NSArray<NSURL*>* urls = [self getModelURLs];
    urls = [urls sortedArrayUsingComparator:
                    ^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2)
                    {
                            NSURL* s2 = obj1;
                            NSURL* s1 = obj2;
                            return [s1.lastPathComponent compare: s2.lastPathComponent options: NSNumericSearch];
                    }];

    for (NSURL* url in urls)
    {
        NSDictionary* modelHashes = [self getHashesForModelAtURL: url];

        //  Compare the hashes…

        bool matches = true;
        for (NSString* entityKey in storeHashes.allKeys)
        {
            NSString* storeHash = storeHashes[entityKey];
            NSString* modelHash = modelHashes[entityKey];
            if (![storeHash isEqual: modelHash])
            {
                NSLogDebug(@"Model %@ has mismatch on %@", url.lastPathComponent, entityKey);
                matches = false;
            }
        }

        if (matches)
        {
            NSLogDebug(@"Version matches: %@", url.lastPathComponent);
            break;
        }
    }

- (NSArray<NSURL*>*)
getModelURLs
{
    NSBundle* bundle = [NSBundle bundleForClass: [self class]];
    NSArray<NSURL*>* urls = [bundle URLsForResourcesWithExtension: @"mom" subdirectory: @"Model.momd"];
    return urls;
}

- (NSDictionary*)
getHashesForModelAtURL: (NSURL*) inURL
{
    NSManagedObjectModel* model = [[NSManagedObjectModel alloc] initWithContentsOfURL: inURL];
    NSDictionary* hashes = model.entityVersionHashesByName;
    return hashes;
}