核心数据 NSPersistentStoreCoordinator 的 +metadataForPersistentStoreOfType:URL:error: 有时返回 nil
Posted
技术标签:
【中文标题】核心数据 NSPersistentStoreCoordinator 的 +metadataForPersistentStoreOfType:URL:error: 有时返回 nil【英文标题】:Core Data NSPersistentStoreCoordinator's +metadataForPersistentStoreOfType:URL:error: sometimes returns nil 【发布时间】:2014-04-02 16:48:22 【问题描述】:我有一种方法可以通过多个NSManagedObjectModel
版本逐步迁移core data
sqlite
存储,直到存储处于当前版本。该方法的灵感来自 Marcus Zarra 的核心数据书中的代码。
该应用程序正在生产中,但我的方法在大约 0.5% 的情况下失败。当它失败时,它会返回 NO
并使用 Crashlytics 记录错误:
NSSQLiteErrorDomain = 14; NSUnderlyingException = "I/O error for database at <store url>. SQLite error code:14, 'unable to open database file'"
sqlite
存储使用预写日志记录 (WAL
),如果我在第一次手动删除 sqlite-WAL
文件后调用 +metadataForPersistentStoreOfType:URL:error:
,有时会遇到相同的错误。
渐进式迁移代码:
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
toModel:(NSManagedObjectModel*)finalModel
error:(NSError**)error
NSURL *storeDirectoryURL = [sourceStoreURL URLByDeletingLastPathComponent];
NSString *storeExtension = [sourceStoreURL pathExtension];
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
while (![finalModel isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata])
NSManagedObjectModel *sourceModel =
[self managedObjectModelForMetadata:sourceMetadata];
if (!sourceModel) return NO;
NSString *modelName = nil;
NSManagedObjectModel *targetModel =
[self suitableTargetModelForMigrationFromSourceModel:sourceModel
modelName:&modelName];
if (!targetModel) return NO;
NSMigrationManager *manager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:targetModel];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
NSURL *destinationStoreURL =
[[storeDirectoryURL URLByAppendingPathComponent:modelName]
URLByAppendingPathExtension:storeExtension];
BOOL migrated = [manager migrateStoreFromURL:sourceStoreURL
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:error];
if (!migrated) return NO;
NSString *sourceModelName =
[self versionStringForManagedObjectModel:sourceModel];
NSURL *backUpURL = [self backupURLWithDirectoryURL:storeDirectoryURL
pathExtension:storeExtension
modelName:sourceModelName];
BOOL replaced = [self replaceStoreAtURL:sourceStoreURL
withStoreAtURL:destinationStoreURL
backupURL:backUpURL
error:error];
if (replaced == NO) return NO;
sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
return YES;
- (NSManagedObjectModel *)managedObjectModelForMetadata:(NSDictionary *)metadata
for (NSURL *URL in [self modelURLs])
NSManagedObjectModel *model =
[[NSManagedObjectModel alloc] initWithContentsOfURL:URL];
if ([model isConfiguration:nil compatibleWithStoreMetadata:metadata])
return model;
return nil;
- (NSManagedObjectModel *)suitableTargetModelForMigrationFromSourceModel:(NSManagedObjectModel *)sourceModel
modelName:(NSString **)modelName
for (NSURL *modelURL in [self modelURLs])
NSManagedObjectModel *targetModel =
[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
if (mappingModel)
*modelName = [[modelURL lastPathComponent] stringByDeletingPathExtension];
return targetModel;
return nil;
- (NSArray *)modelURLs
NSMutableArray *modelURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:@"mom"
subdirectory:nil] mutableCopy];
NSArray *momdURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:@"momd"
subdirectory:nil] mutableCopy];
for (NSURL *momdURL in momdURLs)
NSString *directory = [momdURL lastPathComponent];
NSArray *array =
[[NSBundle mainBundle] URLsForResourcesWithExtension:@"mom"
subdirectory:directory];
[modelURLs addObjectsFromArray:array];
return [modelURLs copy];
- (NSURL *)backupURLWithDirectoryURL:(NSURL *)URL
pathExtension:(NSString *)extension
modelName:(NSString *)name
NSString *GUID = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *pathComponant = [NSString stringWithFormat:@"%@-%@", GUID, name];
return [[URL URLByAppendingPathComponent:pathComponant]
URLByAppendingPathExtension:extension];
- (BOOL)replaceStoreAtURL:(NSURL *)originalStoreURL
withStoreAtURL:(NSURL *)newStoreURL
backupURL:(NSURL *)backupURL
error:(NSError **)error
BOOL storeMoved = [self moveStoreAtURL:originalStoreURL
toURL:backupURL
error:error];
if (!storeMoved) return NO;
storeMoved = [self moveStoreAtURL:newStoreURL
toURL:originalStoreURL
error:error];
if (!storeMoved) return NO;
return YES;
- (BOOL)moveStoreAtURL:(NSURL *)sourceURL
toURL:(NSURL *)targetURL
error:(NSError **)error
NSMutableArray *sourceURLs = [@[sourceURL] mutableCopy];
NSMutableArray *targetURLs = [@[targetURL] mutableCopy];
NSString *walExtension = @"sqlite-wal";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:walExtension])
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:walExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:walExtension]];
NSString *shmExtension = @"sqlite-shm";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:shmExtension])
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:shmExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:shmExtension]];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [sourceURLs count]; i++)
BOOL fileMoved = [fileManager moveItemAtURL:sourceURLs[i]
toURL:targetURLs[i]
error:error];
if (!fileMoved) return NO;
return YES;
- (BOOL)storeAtURL:(NSURL *)URL hasAccessoryFileWithExtension:(NSString *)extension
NSURL *accessoryURL = [self URLByReplacingExtensionOfURL:URL
withExtension:extension];
return [[NSFileManager defaultManager] fileExistsAtPath:[accessoryURL path]];
- (NSURL *)URLByReplacingExtensionOfURL:(NSURL *)URL withExtension:(NSString *)extension
return [[URL URLByDeletingPathExtension] URLByAppendingPathExtension:extension];
- (NSString *)versionStringForManagedObjectModel:(NSManagedObjectModel *)model
NSString *string = @"";
for (NSString *identifier in model.versionIdentifiers)
string = [string stringByAppendingString:identifier];
return string;
抱歉,代码太长了。
【问题讨论】:
【参考方案1】:可能的原因是您的moveStoreAtURL:toURL:error:
方法。您收到的错误是mentioned in Apple's docs,这是由于无法复制所有持久存储文件的结果。看起来您正试图击中所有这些,但要么 (a) 复制代码中存在我现在找不到的错误,要么 (b) 商店在您的应用程序中“活动”,正在使用持久存储协调器,因此您无法从副本中获得一致的状态。
您可以通过一些调试来修复它,如果您确保商店没有在使用中。更改日志模式会更好,并且可能更可靠,这样您就没有 wal
和 shm
文件(该链接也描述了这些文件)。更好的是,如果您的存储文件不是太大,请使用 migratePersistentStore:toURL:options:withType:error
让 Core Data 进行复制。虽然在某些情况下它可能会占用过多的内存,但这应该可以保证正常工作。
【讨论】:
谢谢汤姆。我不认为存储是“活动的”,因为迁移发生在实例化持久存储协调器之前。持久存储协调器本身是延迟创建的,但我很确定它不会被任何其他线程访问,直到迁移发生很长时间后。不过,我将添加日志记录来检查这一点。不过我会看看你建议的方法。 嗨,汤姆。我将根据您的建议使用 migratePersistentStore:toURL:options:withType:error 来移动商店。我不是 100% 确定这会解决我的问题,但似乎是一种更安全的做事方式,所以这是有道理的。感谢您的帮助!【参考方案2】:我正在使用NSMigrationManager
,所以我不能使用NSPersistentStoreCoordinator
's - migratePersistentStore...
,所以我的解决方案是强制检查点操作:
- (void)performCheckpointStoreWithSourceModel:(NSManagedObjectModel *)sourceModel sourceStoreURL:(NSURL *)sourceStoreURL
NSPersistentStoreCoordinator *tempPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:sourceModel];
[tempPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:@NSSQLitePragmasOption: @@"journal_mode": @"DELETE" error:nil];
[tempPSC removePersistentStore:[tempPSC persistentStoreForURL:sourceStoreURL] error:nil];
...在使用 NSMigrationManager 执行迁移之前:
if (![manager migrateStoreFromURL:sourceStoreURL
type:type
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:type
destinationOptions:nil
error:error])
return NO;
【讨论】:
以上是关于核心数据 NSPersistentStoreCoordinator 的 +metadataForPersistentStoreOfType:URL:error: 有时返回 nil的主要内容,如果未能解决你的问题,请参考以下文章