coredata - 转移到app group target

时间:2014-12-02 15:54:01

标签: ios core-data ios8-today-widget

我是Today扩展程序的新手并使用嵌入式框架。

我们的应用目前使用sqlite支持的核心数据。如果我想在应用程序和今天的扩展程序之间共享此内容,我是否应将其移至框架以供两者共享?

如何在应用商店中迁移当前版本以便能够升级到新结构?

4 个答案:

答案 0 :(得分:12)

您需要确保应用和扩展程序都可以使用模型和持久性存储文件。

对于模型,将其移动到框架是一个好主意,因为它意味着模型文件只有一个副本。只要应用程序和扩展程序都链接到框架,它就可以同时使用。如果你这样做,那么在框架中设置核心数据堆栈的代码也是一个好主意,因为它在两种情况下都是相同的。

您当然可以将模型包含在两个目标中。这意味着你将拥有该文件的两个副本,这会浪费空间。可能不是很多空间。

对于持久性存储,您必须设置应用程序组并在组目录中使用存储文件。应用程序组是"功能"中的一个设置。对于应用和扩展程序 - 将其打开并创建一个组名。然后将持久性存储文件放在组目录中,您可以使用

之类的代码找到该目录
NSURL *groupURL = [[NSFileManager defaultManager]
    containerURLForSecurityApplicationGroupIdentifier:
        @"GROUP NAME HERE"];

[我更详细地介绍了其中的一部分at my blog]。

如果您有现有数据,则必须将其移至新商店文件。这看起来像

  1. 检查数据的旧非组副本是否存在
  2. 如果是,请使用该文件设置Core Data堆栈。然后使用migratePersistentStore:toURL:options:withType:error:将其移至新位置。然后删除旧副本。
  3. 如果旧版本不存在,只需像往常一样使用新副本设置核心数据。

答案 1 :(得分:7)

对于旧数据部分的迁移,我做了一些工作。

  1. 如何检查旧数据库是否存在,我使用下面的代码进行检查。

    if ([fileManager fileExistsAtPath:[storeURL path]]) {
    NSLog(@"old single app db exist.");
    targetURL = storeURL;
    needMigrate = true;
    }
    // storeURL is the store url return by:
    - (NSURL *)applicationDocumentsDirectory
    {
       return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    }
    
  2. 将旧数据迁移到新数据存储位置。

  3. 如果旧数据存储存在,并且组数据存储不存在,我使用下面的代码进行迁移:

    if (needMigrate) {
        NSError *error = nil;
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [context setPersistentStoreCoordinator:__persistentStoreCoordinator];
        [__persistentStoreCoordinator migratePersistentStore:store toURL:groupURL options:options withType:NSSQLiteStoreType error:&error];
        if (error != nil) {
            NSLog(@"Error when migration to groupd url %@, %@", error, [error userInfo]);
            abort();
        }
    }
    

    通过检查旧数据是否存在来设置needMigrate标志。

    1. 如果不存在数据,我将使用组URL来创建PSC。
    2. 供您参考,我在此处粘贴完整代码:

      - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
      {
          if (__persistentStoreCoordinator != nil)
          {
             return __persistentStoreCoordinator;
          }
      
      bool needMigrate = false;
      bool needDeleteOld  = false;
      
      NSString *kDbName = @"xxx.sqlite";
      
      NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:kDbName];
      
      NSURL *groupURL = [[self applicationGroupDocumentDirectory] URLByAppendingPathComponent:kDbName];
      
      NSURL *targetURL =  nil;
      
      NSFileManager *fileManager = [NSFileManager defaultManager];
      
      if ([fileManager fileExistsAtPath:[storeURL path]]) {
          NSLog(@"old single app db exist.");
          targetURL = storeURL;
          needMigrate = true;
      }
      
      
      if ([fileManager fileExistsAtPath:[groupURL path]]) {
             NSLog(@"group db exist");
             needMigrate = false;
             targetURL = groupURL;
      
          if ([fileManager fileExistsAtPath:[storeURL path]]) {
              needDeleteOld = true;
          }
      }
      
      if (targetURL == nil)
          targetURL = groupURL;
      
      NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @(YES),
                                  NSInferMappingModelAutomaticallyOption: @(YES)};
      
      NSError *error = nil;
      __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
      
      
      NSPersistentStore *store;
      store = [__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:targetURL options:options error:&error];
      
      if (!store)
      {
          /*
           Replace this implementation with code to handle the error appropriately.
      
           abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
      
           Typical reasons for an error here include:
           * The persistent store is not accessible;
           * The schema for the persistent store is incompatible with current managed object model.
           Check the error message to determine what the actual problem was.
      
      
           If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
      
           If you encounter schema incompatibility errors during development, you can reduce their frequency by:
           * Simply deleting the existing store:
           [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
      
           * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
           [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
      
           Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
      
           */
          NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
          abort();
      }
      
      // do the migrate job from local store to a group store.
      if (needMigrate) {
          NSError *error = nil;
          NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
          [context setPersistentStoreCoordinator:__persistentStoreCoordinator];
          [__persistentStoreCoordinator migratePersistentStore:store toURL:groupURL options:options withType:NSSQLiteStoreType error:&error];
          if (error != nil) {
              NSLog(@"Error when migration to groupd url %@, %@", error, [error userInfo]);
              abort();
          }
      }
      
      return __persistentStoreCoordinator;
      }
      
      /**
       Returns the URL to the application's Documents directory.
       */
      - (NSURL *)applicationDocumentsDirectory
      {
      return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
      }
      
      - (NSURL *)applicationGroupDocumentDirectory
      {
      return [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.kzjeef.shitf.scheduler"];
      }
      

答案 2 :(得分:2)

如果有人想在swift中使用解决方案,只需在didFinishLaunchingWithOptions中添加以下函数。

 func migratePersistentStore(){

    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    var storeOptions = [AnyHashable : Any]()
    storeOptions[NSMigratePersistentStoresAutomaticallyOption] = true
    storeOptions[NSInferMappingModelAutomaticallyOption] = true
    let oldStoreUrl = self.applicationDocumentsDirectory.appendingPathComponent("YourApp.sqlite")!
    let newStoreUrl = self.applicationGroupDirectory.appendingPathComponent("YourApp.sqlite")!
    var targetUrl : URL? = nil
    var needMigrate = false
    var needDeleteOld = false

    if FileManager.default.fileExists(atPath: oldStoreUrl.path){
        needMigrate = true
        targetUrl = oldStoreUrl
    }

    if FileManager.default.fileExists(atPath: newStoreUrl.path){
        needMigrate = false
        targetUrl = newStoreUrl

        if FileManager.default.fileExists(atPath: oldStoreUrl.path){
            needDeleteOld = true
        }
    }
    if targetUrl == nil {
        targetUrl = newStoreUrl
    }
    if needMigrate {
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl!, options: storeOptions)
            if let store = coordinator.persistentStore(for: targetUrl!) 
             {
                do {
                    try coordinator.migratePersistentStore(store, to: newStoreUrl, options: storeOptions, withType: NSSQLiteStoreType)

                } catch let error {
                    print("migrate failed with error : \(error)")
                }
            }
        } catch let error {
            CrashlyticsHelper.reportCrash(err: error as NSError, strMethodName: "migrateStore")
        }
    }
  if needDeleteOld {
        DBHelper.deleteDocumentAtUrl(url: oldStoreUrl)
        guard let shmDocumentUrl = self.applicationDocumentsDirectory.appendingPathComponent("NoddApp.sqlite-shm") else { return }
        DBHelper.deleteDocumentAtUrl(url: shmDocumentUrl)
        guard let walDocumentUrl = self.applicationDocumentsDirectory.appendingPathComponent("NoddApp.sqlite-wal") else { return }
        DBHelper.deleteDocumentAtUrl(url: walDocumentUrl)
    }
}

My PersistentStoreCoordinator看起来像这样:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationGroupDirectory.appendingPathComponent("YourApp.sqlite")
    var storeOptions = [AnyHashable : Any]()
    storeOptions[NSMigratePersistentStoresAutomaticallyOption] = true
    storeOptions[NSInferMappingModelAutomaticallyOption] = true
    var failureReason = "There was an error creating or loading the application's saved data."
    do {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options:storeOptions)
    } catch {
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
        dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?

        dict[NSUnderlyingErrorKey] = error as NSError
        let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
        abort()
    }
    return coordinator
}()

当appstore中已有应用并且您希望将coreData持久存储文件从默认存储位置迁移到应用组位置时,就会出现这种情况。

编辑:对于从旧位置删除文件,建议我们使用NSFileCoordinator来执行任务。

static func deleteDocumentAtUrl(url: URL){
    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
    fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: {
        (urlForModifying) -> Void in
        do {
            try FileManager.default.removeItem(at: urlForModifying)
        }catch let error {
            print("Failed to remove item with error: \(error.localizedDescription)")
        }
    })
}

请注意NSFileCoordinator用于删除文件的原因是因为NSFileCoordinator允许我们确保文件相关的任务(如打开阅读编写)以不会干扰系统上任何其他任务的方式完成相同的文件。例如,如果你想打开一个文件,同时它被删除,你不希望这两个动作同时发生。

成功迁移商店后,请调用上述功能。

答案 3 :(得分:2)

对于swift 3.0及更高版本中的迁移,只需将下面的persistentStoreCoordinator方法替换为你的

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {

    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let options = [
        NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true
    ]

    let oldStoreUrl = self.applicationDocumentsDirectory.appendingPathComponent("TaskTowerStorage.sqlite")
    let directory: NSURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppGroupID)! as NSURL
    let newStoreUrl = directory.appendingPathComponent("YourDatabaseName.sqlite")!

    var targetUrl : URL? = nil
    var needMigrate = false
    var needDeleteOld = false


    if FileManager.default.fileExists(atPath: oldStoreUrl.path){
        needMigrate = true
        targetUrl = oldStoreUrl
    }
    if FileManager.default.fileExists(atPath: newStoreUrl.path){
        needMigrate = false
        targetUrl = newStoreUrl

        if FileManager.default.fileExists(atPath: oldStoreUrl.path){
            needDeleteOld = true
        }
    }
    if targetUrl == nil {
        targetUrl = newStoreUrl
    }

    if needMigrate {
        do {
            try coordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl!, options: options)
            if let store = coordinator?.persistentStore(for: targetUrl!)
            {
                do {
                    try coordinator?.migratePersistentStore(store, to: newStoreUrl, options: options, withType: NSSQLiteStoreType)

                } catch let error {
                    print("migrate failed with error : \(error)")
                }
            }
        } catch let error {
            //CrashlyticsHelper.reportCrash(err: error as NSError, strMethodName: "migrateStore")
            print(error)
        }
    }

    if needDeleteOld {
        self.deleteDocumentAtUrl(url: oldStoreUrl)
        self.deleteDocumentAtUrl(url: self.applicationDocumentsDirectory.appendingPathComponent("YourDatabaseName.sqlite-shm"))
        self.deleteDocumentAtUrl(url: self.applicationDocumentsDirectory.appendingPathComponent("YourDatabaseName.sqlite-wal"))
    }

    do {
        try coordinator!.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: targetUrl, options: options)
    } catch var error as NSError {
        coordinator = nil
        NSLog("Unresolved error \(error), \(error.userInfo)")
        abort()
    } catch {
        fatalError()
    }
    return coordinator

}() 
     lazy var applicationDocumentsDirectory: URL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named 'Bundle identifier' in the application's documents Application Support directory.
    let urls = Foundation.FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return urls[urls.count-1]
}()


lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "StorageName", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()