+[NSManagedObjectModel mergeModelFromBundles::forStoreMetadata:] 总是返回 nil

Posted

技术标签:

【中文标题】+[NSManagedObjectModel mergeModelFromBundles::forStoreMetadata:] 总是返回 nil【英文标题】:+[NSManagedObjectModel mergedModelFromBundles::forStoreMetadata:] always returns nil 【发布时间】:2019-04-06 02:44:10 【问题描述】:

我有一个包含 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 的助手,并且没有做任何更改。

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

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

【问题讨论】:

【参考方案1】:

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

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

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

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

现在使用 SQLite 查看器或sqlite3 命令行工具打开一个商店数据库文件。在该数据库中,除了模型中每个实体的一个表之外,您将看到一个名为 Z_METADATA 的表,其中包含一行和三列。名为Z_PLIST 的列的值被键入为二进制数据块。将该数据复制到一个文件中,使用扩展名.plist 命名,双击它,它会在您最喜欢的 plist 编辑器中打开,因为该数据实际上是表示 XML 格式的 Apple 属性列表的文本字符串。它的键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 进行比较,并返回其 model hashes 匹配所有实体的存储哈希,两边都没有额外的不匹配实体。

因此,您会发现手动进行比较有点乏味。但可能在探索这些 plists 时,你会发现额头拍打。如果没有,请给我们一些关于您粘贴的代码的更多上下文,可能有人可以提供帮助。

【讨论】:

当我发布我的答案时,您可能一直在撰写该答案。很抱歉浪费您的时间!无论如何,我想通了(你可以看到它一直是用户错误)。不过,您提供了一些关于 SQLite DB 的有用信息,谢谢! 没问题,瑞克。这不是浪费。有时会发生这种情况。【参考方案2】:

啊。事实证明,我编辑了最新的模型版本,而不是添加一个新版本。这就是为什么没有人会匹配。一旦我恢复了最新版本并添加了带有更改的新模型版本,即使没有默认映射模型,它也能正常工作。

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

    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;

【讨论】:

以上是关于+[NSManagedObjectModel mergeModelFromBundles::forStoreMetadata:] 总是返回 nil的主要内容,如果未能解决你的问题,请参考以下文章

NSManagedObject、NSManagedObjectContext 和 NSManagedObjectModel 的区别

NSManagedObjectModel 合并模型FromBundles 错误

尝试使用前向类“NSManagedObjectModel”作为 Swift 类模型的超类

NSInternalConsistencyException 原因 +entityForName:找不到实体名称的 NSManagedObjectModel

+entityForName: 找不到实体名称的 NSManagedObjectModel

NSManagedObjectModel initWithContentsOfURL 返回 nil 即使 modelURL 是有效的