Ios 核心数据NSPersistentStoreCoordinator';s+;metadataForPersistentStoreOfType:URL:error:有时返回nil

Ios 核心数据NSPersistentStoreCoordinator';s+;metadataForPersistentStoreOfType:URL:error:有时返回nil,ios,objective-c,core-data,core-data-migration,Ios,Objective C,Core Data,Core Data Migration,我有一种方法,可以通过多个NSManagedObjectModel版本逐步迁移核心数据sqlite存储,直到该存储位于当前版本。该方法的灵感来自Marcus Zarra的核心数据书中的代码 该应用程序正在生产中,但我的方法在大约0.5%的情况下失败。当失败时,它返回NO,并使用Crashlytics记录错误: NSSQLiteErrorDomain=14;NSUnderlyingException=“位于.SQLite的数据库的I/O错误错误代码:14,‘无法打开数据库文件’” sqlite存储

我有一种方法,可以通过多个
NSManagedObjectModel
版本逐步迁移
核心数据
sqlite
存储,直到该存储位于当前版本。该方法的灵感来自Marcus Zarra的核心数据书中的代码

该应用程序正在生产中,但我的方法在大约0.5%的情况下失败。当失败时,它返回
NO
,并使用Crashlytics记录错误:

NSSQLiteErrorDomain=14;NSUnderlyingException=“位于.SQLite的数据库的I/O错误错误代码:14,‘无法打开数据库文件’”

sqlite
存储使用的是预写日志(
WAL
),如果在首次手动删除
sqlite-WAL
文件后调用
+metadataForPersistentStoreOfType:URL:error:
,有时会出现相同的错误

渐进式迁移代码:
-(BOOL)渐进迁移URL:(NSURL*)源存储URL
toModel:(NSManagedObjectModel*)最终模型
错误:(n错误**)错误
{
NSURL*storeDirectoryURL=[sourceStoreURL URLByDeletingLastPathComponent];
NSString*storeExtension=[sourceStoreURL路径扩展];
NSDictionary*sourceMetadata=[NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
错误:错误];
如果(!sourceMetadata)返回否;
而(![最终模型配置:无
compatibleWithStoreMetadata:sourceMetadata]){
NSManagedObjectModel*源模型=
[self-ManagedObjectModelFormMetadata:sourceMetadata];
如果(!sourceModel)返回否;
NSString*modelName=nil;
NSManagedObjectModel*targetModel=
[自适性TargetModelFormigrationFromSourceModel:sourceModel
modelName:&modelName];
如果(!targetModel)返回否;
NSMigrationManager*管理器=
[[NSMigrationManager alloc]initWithSourceModel:sourceModel
destinationModel:targetModel];
NSMappingModel*mappingModel=
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
NSURL*目的地存储URL=
[[storeDirectoryURL URLByAppendingPathComponent:modelName]
URLByAppendingPathExtension:storeExtension];
BOOL migrated=[manager migrateStoreFromURL:sourceStoreURL
类型:NSSQLiteStoreType
选项:无
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:NSSQLiteStoreType
目的选项:无
错误:错误];
如果(!migrated)返回否;
NSString*sourceModelName=
[自版本字符串FormanageDobjectModel:sourceModel];
NSURL*backUpURL=[自备份UrlWithDirectoryUrl:storeDirectoryURL
路径扩展:storeExtension
modelName:sourceModelName];
BOOL replaced=[self-replaceStoreAtURL:sourceStoreURL
带存储属性:DestinationsStoreUrl
backupURL:backupURL
错误:错误];
如果(替换==否),则返回否;
sourceMetadata=[NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
错误:错误];
如果(!sourceMetadata)返回否;
}
返回YES;
}
-(NSManagedObjectModel*)ManagedObjectModelFormMetadata:(NSDictionary*)元数据
{
for(NSURL*URL在[自建模URL]中){
NSManagedObjectModel*模型=
[[NSManagedObjectModel alloc]initWithContentsOfURL:URL];
if([model isConfiguration:nil compatibleWithStoreMetadata:metadata]){
收益模型;
}
}
返回零;
}
-(NSManagedObjectModel*)适用的TargetModelFormigrationfromSourceModel:(NSManagedObjectModel*)sourceModel
modelName:(NSString**)modelName
{
对于(在[self-modelURLs]中的NSURL*modelURL){
NSManagedObjectModel*targetModel=
[[NSManagedObjectModel alloc]initWithContentsOfURL:modelURL];
NSMappingModel*mappingModel=
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
if(映射模型){
*modelName=[[modelURL lastPathComponent]stringByDeletingPathExtension];
返回目标模型;
}
}
返回零;
}
-(NSArray*)模型URL
{
NSMutableArray*模型URL=
[[[NSBundle mainBundle]URLSforsourceswithextension:@“mom”
子目录:nil]mutableCopy];
NSArray*momdURLs=
[[[NSBundle mainBundle]URLSforsourceswithextension:@“momd”
子目录:nil]mutableCopy];
用于(NSURL*momdURL在momdURLs中){
NSString*目录=[momdURL lastPathComponent];
NSArray*数组=
[[NSBundle mainBundle]URLSforsourceswitheXTE
- (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;
}
- (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];
}
if (![manager migrateStoreFromURL:sourceStoreURL
                             type:type
                          options:nil
                 withMappingModel:mappingModel
                 toDestinationURL:destinationStoreURL
                  destinationType:type
               destinationOptions:nil
                            error:error]) {
    return NO;
}