+[NSManagedObjectModel mergedModelFromBundles::forStoreMetadata:] 总是 returns 无

+[NSManagedObjectModel mergedModelFromBundles::forStoreMetadata:] always returns nil

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

关键是调用

    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版。

现在,对模型的最后更改相当微不足道(添加了几个可选字段),所以我认为它可以自动推断映射,但那不起作用,所以我添加了一个映射使用 Xcode 的助手从版本 14 到版本 15 的模型,没有做任何更改。

知道为什么它返回 nil,或者我可以做些什么来进一步调查这个问题?

同样,当我说 "version 14" 时,我指的是 .xcdatamodel 文件的顺序编号。有什么方法可以查看实际商店并确定 Core Data 认为它是哪个版本的模型?

啊。事实证明,我编辑了最新的模型版本,而不是添加新的。这就是 none 匹配的原因。一旦我还原了最新版本并添加了具有更改的新模型版本,即使没有默认映射模型,它也能正常工作。

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

    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;
}

嗯,首先,您似乎知道自己在做什么,经历了 14 次核心数据迁移等等。所以我认为你应该留意一些愚蠢的拍额头类型的错误。

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

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

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

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

最后,回答你的第二个问题,传递给 +[NSManagedObjectModel mergedModelFromBundles:forStoreMetadata:]storeMetadata 实际上是来自商店的 Z_METADATA,其中包含那些 商店哈希+[NSManagedObjectModel mergedModelFromBundles:forStoreMetadata:] 将这些 store hashes 与传入 bundle 中每个候选数据模型的 model hashes 进行比较,并且returns model hashes 匹配所有实体的 store hashes 的模型,两边都没有额外的不匹配实体。

所以你看手动比较有点乏味。但可能在探索这些 plists 时,你会发现那个前额拍打器。如果没有,请为我们提供有关您粘贴的代码的更多背景信息,也许有人可以提供帮助。