13

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

我们的应用程序目前使用由 sqlite 支持的核心数据。如果我想在应用程序和今天的扩展程序之间共享它,我应该将它移动到一个框架以供两者共享吗?

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

4

4 回答 4

14

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

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

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

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

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

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

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

  1. 检查数据的旧非组副本是否存在
  2. 如果是,请使用该文件设置 Core Data 堆栈。然后使用migratePersistentStore:toURL:options:withType:error:将其移动到新位置。然后删除旧副本。
  3. 如果旧版本不存在,只需像往常一样使用新副本设置 Core Data。
于 2014-12-02T19:37:44.063 回答
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. 将旧数据迁移到新的数据存储位置。

如果旧数据存储存在,而组数据存储不存在,我使用以下代码进行迁移:

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"];
}
于 2015-06-07T06:55:09.717 回答
5

如果有人想要快速解决方案,只需在 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
}()

当应用商店中已经有一个应用程序并且您希望将 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 允许我们确保文件相关的任务(例如打开读写)以不会干扰系统上尝试使用同一文件的任何其他任务的方式完成.例如,如果你想打开一个文件,同时它被删除,你不希望这两个动作同时发生。

请在商店迁移成功后调用上述函数。

于 2017-11-29T11:36:13.893 回答
3

对于 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)!
}()
于 2017-12-08T11:27:55.950 回答