coredata – 转移到应用组目标

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

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

如何在app store中迁移当前版本以便能够升级到新结构?

如果有人想在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) } } 

我的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允许我们确保文件相关的任务(如打开阅读编写)以这样的方式完成,即不会干扰尝试使用同一文件的系统上的任何其他任务.Eg如果你想打开一个文件,同时它被删除,你不希望这两个动作同时发生。

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

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

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

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

对于持久性存储,您必须设置应用程序组并在组目录中使用存储文件。 应用程序组是应用程序和扩展程序“function”中的设置之一 – 将其打开并创建组名称。 然后将持久性存储文件放在组目录中,您可以使用代码找到它

 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: @"GROUP NAME HERE"]; 

[我在博客中详细介绍了其中的一些内容]。

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

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

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

  1. 如何检查旧的db是否存在,我用下面的代码来检查。

     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. 将旧数据迁移到新数据存储位置。

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

 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。

供您参考,我在这里粘贴完整代码:

 - (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"]; } 

对于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)! }()