+[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 时,你会发现那个前额拍打器。如果没有,请为我们提供有关您粘贴的代码的更多背景信息,也许有人可以提供帮助。
我有一个 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 时,你会发现那个前额拍打器。如果没有,请为我们提供有关您粘贴的代码的更多背景信息,也许有人可以提供帮助。