Skip to content

Instantly share code, notes, and snippets.

@kean
Last active June 20, 2016 20:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kean/934c69d5b567e3d4c9a6f512542a59ee to your computer and use it in GitHub Desktop.
Save kean/934c69d5b567e3d4c9a6f512542a59ee to your computer and use it in GitHub Desktop.
DFCoreDataMigration.m
// The MIT License (MIT)
//
// Copyright (c) 2016 Alexander Grebenyuk (github.com/kean).
#import "DFCoreDataMigration.h"
NSString *const DFMigrationErrorDomain = @"DFMigrationErrorDomain";
@implementation DFMigrationManager
- (instancetype)initWithStoreURL:(NSURL *)storeURL storeType:(NSString *)storeType orderedModels:(NSArray<NSManagedObjectModel *> *)models {
NSParameterAssert(storeURL);
NSParameterAssert(storeType);
NSParameterAssert(models.count > 0);
if (self = [super init]) {
_storeURL = [storeURL copy];
_storeType = [storeType copy];
_moms = [models copy];
_bundles = [NSBundle allBundles];
}
return self;
}
- (BOOL)migrate:(NSError *__autoreleasing _Nullable *)error {
NSInteger modelIndex = [self _indexOfCompatibleMom:error];
if (modelIndex == NSNotFound) {
if (error != NULL && *error == nil) {
*error = [NSError errorWithDomain:DFMigrationErrorDomain code:DFMigrationErrorSourceModelNotFound userInfo:@{ NSLocalizedDescriptionKey: @"Failed to find a managed object model compatible with the persistent store." }];
}
return NO;
}
if (modelIndex == (self.moms.count - 1)) {
return YES; // Migrated to the final model
}
@autoreleasepool {
NSManagedObjectModel *smom = self.moms[modelIndex];
NSManagedObjectModel *dmom = self.moms[modelIndex+1];
if (![self _migrateFromMom:smom toMom:dmom error:error]) {
return NO;
}
}
return [self migrate:error];
}
- (NSInteger)_compatibleMomIndex:(NSError **)error {
NSDictionary *meta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:self.storeType URL:self.storeURL options:self.storeOptions error:error];
return meta ? [self.moms indexOfObjectPassingTest:^BOOL(NSManagedObjectModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [obj isConfiguration:self.storeConfiguraton compatibleWithStoreMetadata:meta];
}] : NSNotFound;
}
- (BOOL)_migrateFromMom:(NSManagedObjectModel *)smom toMom:(NSManagedObjectModel *)dmom error:(NSError *__autoreleasing _Nullable *)error {
// Create temp directory for destination store
NSURL *tmpDir = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:[NSUUID UUID].UUIDString];
if (![[NSFileManager defaultManager] createDirectoryAtURL:tmpDir withIntermediateDirectories:YES attributes:nil error:error]) {
return NO;
}
BOOL didMigrate = [self _migrateFromMom:smom toMom:dmom tmpDir:tmpDir error:error];
// Cleanup
[[NSFileManager defaultManager] removeItemAtURL:tmpDir error:nil];
return didMigrate;
}
- (BOOL)_migrateFromMom:(NSManagedObjectModel *)smom toMom:(NSManagedObjectModel *)dmom tmpDir:(NSURL *)tmpDir error:(NSError *__autoreleasing _Nullable *)error {
NSArray<NSMappingModel *> *mappings = [self _mappingsFromMom:smom toMom:dmom error:error];
if (!mappings.count) {
return NO;
}
// Migrate store to temp destination
NSURL *destinationURL = [tmpDir URLByAppendingPathComponent:self.storeURL.lastPathComponent];
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:smom destinationModel:dmom];
for (NSMappingModel *mapping in mappings) {
@autoreleasepool {
if (![manager migrateStoreFromURL:self.storeURL
type:self.storeType
options:self.storeOptions
withMappingModel:mapping
toDestinationURL:destinationURL
destinationType:self.storeType
destinationOptions:self.storeOptions
error:error]) {
return NO;
}
}
}
// Overwrites source store with the migrated store.
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:dmom];
if (![psc replacePersistentStoreAtURL:self.storeURL
destinationOptions:self.storeOptions
withPersistentStoreFromURL:destinationURL
sourceOptions:self.storeOptions
storeType:self.storeType
error:error]) {
return NO;
}
return YES;
}
- (NSArray<NSMappingModel *> *)_mappingsFromMom:(NSManagedObjectModel *)smom toMom:(NSManagedObjectModel *)dmom error:(NSError *__autoreleasing _Nullable *)error {
// Find custom mapping
NSMappingModel *mapping = [NSMappingModel mappingModelFromBundles:self.bundles forSourceModel:smom destinationModel:dmom];
if (!mapping) {
// Find inferred mapping (lightweight migration)
mapping = [NSMappingModel inferredMappingModelForSourceModel:smom destinationModel:dmom error:error];
}
if (!mapping) {
if (error != NULL && *error == nil) {
*error = [NSError errorWithDomain:DFMigrationErrorDomain code:DFMigrationErrorMappingModelNotFound userInfo:@{ NSLocalizedDescriptionKey: @"Failed to find mapping model" }];
}
}
return mapping ? @[mapping] : nil;
}
@end
@implementation DFMigrationManager (Convenience)
+ (BOOL)migrateStoreAtURL:(NSURL *)storeURL storeType:(NSString *)storeType orderedModels:(NSArray<NSManagedObjectModel *> *)models error:(NSError *__autoreleasing _Nullable *)error {
return [[[DFMigrationManager alloc] initWithStoreURL:storeURL storeType:storeType orderedModels:models] migrate:error];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment