05-自己建立mapmodel自定義遷移方式

葉喬木發表於2019-01-09

自動建立Mapping

如果模型的改變很大或者不支援輕量級資料遷移的條件,則我們需要進行自定義遷移。

使用對映模型 適用於更加複雜的資料的遷移

NSMappingModel

類似於資料模型

NSEntityMapping

告知遷移過程如何在目標資料儲存中處理源實體的對映。

對映型別決定了如何處理目標資料儲存中的特定實體。對映型別有新增 移除 複製 變換。

  • 新增對映:一個目標中的新實體新增到目標資料儲存中
  • 移除對映:實體不存在目標對映中,只存在於源中。
  • 複製對映:將源物件完全相同地複製到目標
  • 變換對映: 實體存在於源和目標中並且對映應該以某種方式將源變換到目標。

NSPropertyMapping

告知遷移過程如何將源屬性對映到目標屬性。

在從源資料儲存到目標資料儲存移動資料是可以提供一個值表示式來轉換值。

開始一個自定義的遷移方式

1 新建一個mapModel

  • 確保Data Model 處於選中狀態
  • File > New > File> iOS > Core Data > Mapping Model , 並點選Next 按鈕
  • 將舊版本的設定為Source Data Model 並點選Next 按鈕 ,將新建的設定為Target Data Model, 並點選Next 按鈕
  • 儲存mappingModel
    在這裡插入圖片描述

2 程式碼的實現

首先要判斷本地資料庫檔案和當前使用資料模型的結構是否相同。

如果不相同的話進行遷移。

1 在開啟本地資料庫的時候就要進行判斷

// 判斷是否需要遷移 是否已經遷移過了
- (BOOL)isHaveMigration
{
    // 原來的資料庫路徑 不存在直接進行返回
    
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  NSUserDomainMask, YES) lastObject];
    NSString *sqlPath = [docStr stringByAppendingPathComponent:@"student.sqlite"];
    NSURL *sqlURL = [NSURL fileURLWithPath:sqlPath];
    
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:sqlPath]) {
        return NO;
    }
    
    // 比較存在模型資料的後設資料
    
    NSError *error =  nil;
    
    // 資料庫的資料結構
    NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sqlURL options:nil error:&error];
    
    // 比較資料庫的資料 和目標資料是否相同 如果相同 不需要遷移操作
    NSManagedObjectModel  *destinationModel = _coord.managedObjectModel;
    if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetaData]) {
        return NO;
    }
    
    return YES;
    
    
}

2 如果需要遷移 則進行遷移操作

- (BOOL)startMigration
{
    BOOL success = NO;
    NSError *error = nil;
    
    // 舊的資料庫路徑
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  NSUserDomainMask, YES) lastObject];
    NSString *sqlPath = [docStr stringByAppendingPathComponent:@"student.sqlite"];
    NSURL *sqlURL = [NSURL fileURLWithPath:sqlPath];
    
    
    // 原來的資料模型資訊
    NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sqlURL options:nil error:&error];
    
    
    // 原來的資料模型
    NSManagedObjectModel *soureceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetaData];
    // 當前的資料模型資訊
    NSManagedObjectModel  *destinationModel = _coord.managedObjectModel;
    
    // 載入資料遷移的模型 mappingModel
    NSMappingModel *mapModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:soureceModel destinationModel:destinationModel];
    
    if (mapModel) {
        // 遷移管理器
        NSMigrationManager *mgr = [[NSMigrationManager alloc]initWithSourceModel:soureceModel destinationModel:destinationModel];
        
        
        // 監聽的資料遷移的進度
        // 監聽的進度可以用來給使用者進行展示 例如說QQ新版本更新完之後開啟之後,有一個進度條會更新資料,應該就是這個操作
        [mgr addObserver:self
              forKeyPath:@"migrationProgress"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

        // 先把模型儲存到臨時的sqlite 遷移完成再進行替換操作
        // 目的路徑
        NSString *destPath = [docStr stringByAppendingPathComponent:@"temp2.sqlite"];
        NSURL *destURL = [NSURL fileURLWithPath:destPath];
        success = [mgr migrateStoreFromURL:sqlURL type:NSSQLiteStoreType options:nil withMappingModel:mapModel toDestinationURL:destURL destinationType:NSSQLiteStoreType destinationOptions:nil error:&error];
        
        if (success) {
            // 證明資料遷移成功
            [self replaceStore:sqlURL withStore:destURL];
        }else{
            // 遷移資料失敗
            NSLog(@"%@",error.description);
        }
        
    }
    
    return success;
}

// 響應監聽的方法
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    
    if ([keyPath isEqualToString:@"migrationProgress"]) {
        
        dispatch_async(dispatch_get_main_queue(), ^{
            
            float progress =
            [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
            
            int percentage = progress * 100;
            NSString *string =
            [NSString stringWithFormat:@"Migration Progress: %i%%",
             percentage];
            NSLog(@"%@",string);
            
        });
    }
}



// 將舊的移除新的放置在舊的上面
-(BOOL)replaceStore:(NSURL *)old withStore:(NSURL *)new
{
    BOOL success = NO;
    NSError *error = nil;
    if ([[NSFileManager defaultManager] removeItemAtURL:old error:&error]) {
        error = nil;
        if ([[NSFileManager defaultManager] moveItemAtURL:new toURL:old error:&error]) {
            success = YES;
        }
    }
    return success;
}


相關文章