简体   繁体   English

iOS 11+ 如何将现有核心数据迁移到共享应用组以用于扩展?

[英]iOS 11+ How to migrate existing Core Data to Shared App Group for use in extension?

When I created an iOS 11 app using the core data template, it auto generated the following code in AppDelete.m.当我使用核心数据模板创建 iOS 11 应用程序时,它会在 AppDelete.m 中自动生成以下代码。

synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"My_History"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // 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 parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }

    return _persistentContainer;
}

- (void)saveContext {
NSManagedObjectContext *context = self.persistentContainer.viewContext;
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
    // 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.
    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
    abort();
}

I would like to add a Today and iMessage extension that accesses the history in the core data.我想添加一个访问核心数据历史记录的 Today 和 iMessage 扩展。 From what I read, I need to migrate this data if it exists to a shared app container.根据我阅读的内容,如果此数据存在,我需要将其迁移到共享应用程序容器中。 How would I do that?我该怎么做?

The code is in objective C.代码在目标 C 中。

I have read other questions involving this but all of them seem to be before Apple changed the way core data works to make it easier.我已经阅读了其他涉及此问题的问题,但所有这些问题似乎都是在 Apple 改变核心数据的工作方式以使其更容易之前。 As you can see in my code, I never specified what the data store exact file name is.正如您在我的代码中看到的那样,我从未指定数据存储的确切文件名是什么。 Every example I saw had something like "My_History.sqllite".我看到的每个示例都有类似“My_History.sqllite”的内容。 I don't even know if mine is a sql lite database, it was just created by that code.我什至不知道我的是否是 sql lite 数据库,它只是由该代码创建的。

solidsnake4444 's answer saves my day. solidsnake4444回答节省了我的时间。 Here is the Swift 5.0 version.这是 Swift 5.0 版本。

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "MyApp")
    let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.my.app")!.appendingPathComponent("MyApp.sqlite")

    var defaultURL: URL?
    if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url {
        defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil
    }

    if defaultURL == nil {
        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
    }
    container.loadPersistentStores(completionHandler: { [unowned container] (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }

        if let url = defaultURL, url.absoluteString != storeURL.absoluteString {
            let coordinator = container.persistentStoreCoordinator
            if let oldStore = coordinator.persistentStore(for: url) {
                do {
                    try coordinator.migratePersistentStore(oldStore, to: storeURL, options: nil, withType: NSSQLiteStoreType)
                } catch {
                    print(error.localizedDescription)
                }

                // delete old store
                let fileCoordinator = NSFileCoordinator(filePresenter: nil)
                fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: { url in
                    do {
                        try FileManager.default.removeItem(at: url)
                    } catch {
                        print(error.localizedDescription)
                    }
                })
            }
        }
    })
    return container
}()

I ended up getting it doing the following.我最终得到它执行以下操作。 The sqlite file was actually the name of my init plus .sqlite at the end. sqlite 文件实际上是我的 init 名称加上最后的 .sqlite。

+ (NSPersistentContainer*) GetPersistentContainer {
    //Init the store.
    NSPersistentContainer *_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"Test_App"];

    //Define the store url that is located in the shared group.
    NSURL* storeURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.Test_App"] URLByAppendingPathComponent:@"Test_App.sqlite"];

    //Determine if we already have a store saved in the default app location.
    BOOL hasDefaultAppLocation = [[NSFileManager defaultManager] fileExistsAtPath: _persistentContainer.persistentStoreDescriptions[0].URL.path];

    //Check if the store needs migration.
    BOOL storeNeedsMigration = hasDefaultAppLocation && ![_persistentContainer.persistentStoreDescriptions[0].URL.absoluteString isEqualToString:storeURL.absoluteString];

    //Check if the store in the default location does not exist.
    if (!hasDefaultAppLocation) {
        //Create a description to use for the app group store.
        NSPersistentStoreDescription *description = [[NSPersistentStoreDescription alloc] init];

        //set the automatic properties for the store.
        description.shouldMigrateStoreAutomatically = true;
        description.shouldInferMappingModelAutomatically = true;

        //Set the url for the store.
        description.URL = storeURL;

        //Replace the coordinator store description with this description.
        _persistentContainer.persistentStoreDescriptions = [NSArray arrayWithObjects:description, nil];
    }

    //Load the store.
    [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
        //Check that we do not have an error.
        if (error == nil) {
            //Check if we need to migrate the store.
            if (storeNeedsMigration) {
                //Create errors to track migration and deleting errors.
                NSError *migrateError;
                NSError *deleteError;

                //Store the old location URL.
                NSURL *oldStoreURL = storeDescription.URL;

                //Get the store we want to migrate.
                NSPersistentStore *store = [_persistentContainer.persistentStoreCoordinator persistentStoreForURL: oldStoreURL];

                //Set the store options.
                NSDictionary *storeOptions = @{ NSSQLitePragmasOption : @{ @"journal_mode" : @"WAL" } };

                //Migrate the store.
                NSPersistentStore *newStore = [_persistentContainer.persistentStoreCoordinator migratePersistentStore: store toURL:storeURL options:storeOptions withType:NSSQLiteStoreType error:&migrateError];

                //Check that the store was migrated.
                if (newStore && !migrateError) {
                    //Remove the old SQLLite database.
                    [[[NSFileCoordinator alloc] init] coordinateWritingItemAtURL: oldStoreURL options: NSFileCoordinatorWritingForDeleting error: &deleteError byAccessor: ^(NSURL *urlForModifying) {
                        //Create a remove error.
                        NSError *removeError;

                        //Delete the file.
                        [[NSFileManager defaultManager] removeItemAtURL: urlForModifying error: &removeError];

                        //If there was an error. Output it.
                        if (removeError) {
                            NSLog(@"%@", [removeError localizedDescription]);
                        }
                    }
                     ];

                    //If there was an error. Output it.
                    if (deleteError) {
                        NSLog(@"%@", [deleteError localizedDescription]);
                    }
                }
            }
        } else {
            // 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 parent directory does not exist, cannot be created, or disallows writing.
             * The persistent store is not accessible, due to permissions or data protection when the device is locked.
             * The device is out of space.
             * The store could not be migrated to the current model version.
             Check the error message to determine what the actual problem was.
             */
            NSLog(@"Unresolved error %@, %@", error, error.userInfo);
            abort();
        }
    }];

    //Return the container.
    return _persistentContainer;
}

UPDATE:更新:

To migrating an existing persistent store, the NSPersistentContainer contains the persistentStoreCoordinator , an instance of NSPersistentStoreCoordinator .为了迁移现有的持久化存储, NSPersistentContainer包含了persistentStoreCoordinator ,它是NSPersistentStoreCoordinator的一个实例。 This exposes the method migratePersistentStore:toURL:options:withType:error: to migrate a persistent store.这公开了migratePersistentStore:toURL:options:withType:error:方法来迁移持久存储。

I would do the following:我会做以下事情:

// Get the reference to the persistent store coordinator
let coordinator = persistentContainer.persistentStoreCoordinator
// Get the URL of the persistent store
let oldURL = persistentContainer.persistentStoreDescriptions.url
// Get the URL of the new App Group location
let newURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("YOUR_APP_GROUP")
// Get the reference to the current persistent store
let oldStore = coordinator.persistentStore(for: oldURL)
// Migrate the persistent store
do {
   try coordinator.migratePersistentStore(oldStore, to: newURL, options: nil, withType: NSSQLiteStoreType)
} catch {
   // ERROR
}

Please note that the above hasn't been tested and I haven't handled Optionals so it isn't complete.请注意,以上内容尚未经过测试,并且我没有处理 Optionals,因此它不完整。 Also, I apologise for it being in Swift.另外,我很抱歉它在 Swift 中。 Hopefully it is easy enough for you to write the equivalent in Objective-C.希望你可以很容易地用 Objective-C 编写等价的代码。

ORIGINAL:原来的:

The following outlines how to create an NSPersistentContainer interfacing to a persistent store in a non-default location.下面概述了如何创建一个NSPersistentContainer接口到一个非默认位置的持久存储。

The NSPersistentContainer exposes the defaultDirectoryURL , and states: NSPersistentContainer公开了defaultDirectoryURL ,并声明:

This method returns a platform-dependent NSURL at which the persistent store(s) will be located or are currently located.此方法返回一个依赖于平台的NSURL ,持久存储将位于或当前位于该 NSURL。 This method can be overridden in a subclass of NSPersistentContainer .可以在NSPersistentContainer的子类中重写此方法。

If you subclass NSPersistentContainer and define the defaultDirectoryURL to be an App Group directory using containerURLForSecurityApplicationGroupIdentifier , you should then be able to access the container between your application and extensions (assuming they have the same App Group entitlements).如果您将NSPersistentContainer子类化并使用containerURLForSecurityApplicationGroupIdentifierdefaultDirectoryURL定义为 App Group 目录,那么您应该能够访问您的应用程序和扩展之间的容器(假设它们具有相同的 App Group 权利)。

NSPersistentContainer also exposes persistentStoreDescriptions which also features a URL instance. NSPersistentContainer还公开了还具有 URL 实例的persistentStoreDescriptions Likewise, you may be able to update this to the App Group URL before calling loadPersistentStoresWithCompletionHandler: .同样,您可以在调用loadPersistentStoresWithCompletionHandler:之前将其更新为 App Group URL。

Note that I have not used NSPersistentContainer , and have no idea whether this sharing would cause any concurrency issues.请注意,我没有使用NSPersistentContainer ,并且不知道这种共享是否会导致任何并发问题。

When moving NSPersistentCloudKitContainer , this is what worked for me.移动NSPersistentCloudKitContainer时,这对我有用。 A couple things to watch out for:有几点需要注意:

  • You need to ensure cloudKitContainerOptions is set on the persistent store description otherwise sync will stop working您需要确保在持久存储描述上设置了cloudKitContainerOptions否则同步将停止工作
  • Do not call migratePersistentStore otherwise you'll end up with duplicate records, instead use replacePersistentStore - see this thread for more info不要调用migratePersistentStore否则你会得到重复的记录,而是使用replacePersistentStore - 有关更多信息,请参阅此线程
  • You'll probably want to delete your old database once successfully migrated, destroyPersistentStore sounds promising but doesn't seem to actually delete the files - see this question成功迁移后,您可能希望删除旧数据库, destroyPersistentStore听起来很有希望,但似乎并没有真正删除文件 - 请参阅此问题
struct PersistenceController {
    static let shared = PersistenceController()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "MyCuteDB")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        } else {
            // Use App Groups so app and extensions can access database
            let sharedStoreURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourdomain.yourapp")!.appendingPathComponent("\(container.name).sqlite")
            let defaultStoreURL = container.persistentStoreDescriptions.first!.url! //the URL always starts out in the default location

            // move database to shared location if needed
            if FileManager.default.fileExists(atPath: defaultStoreURL.path) && !FileManager.default.fileExists(atPath: sharedStoreURL.path) {
                let coordinator = container.persistentStoreCoordinator
                do {
                    try coordinator.replacePersistentStore(at: sharedStoreURL, destinationOptions: nil, withPersistentStoreFrom: defaultStoreURL, sourceOptions: nil, ofType: NSSQLiteStoreType)
                    try? coordinator.destroyPersistentStore(at: defaultStoreURL, ofType: NSSQLiteStoreType, options: nil)
                    
                    // destroyPersistentStore says it deletes the old store but it actually truncates so we'll manually delete the files
                    NSFileCoordinator(filePresenter: nil).coordinate(writingItemAt: defaultStoreURL.deletingLastPathComponent(), options: .forDeleting, error: nil, byAccessor: { url in
                        try? FileManager.default.removeItem(at: defaultStoreURL)
                        try? FileManager.default.removeItem(at: defaultStoreURL.deletingLastPathComponent().appendingPathComponent("\(container.name).sqlite-shm"))
                        try? FileManager.default.removeItem(at: defaultStoreURL.deletingLastPathComponent().appendingPathComponent("\(container.name).sqlite-wal"))
                        try? FileManager.default.removeItem(at: defaultStoreURL.deletingLastPathComponent().appendingPathComponent("ckAssetFiles"))
                    })
                } catch {
                    //TODO: Handle error
                }
            }

            // change URL from default to shared location
            container.persistentStoreDescriptions.first!.url = sharedStoreURL

            let description = container.persistentStoreDescriptions.first!
            description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.yourdomain.yourapp")
        }
        
        container.loadPersistentStores { description, error in
            //TODO: Handle error
        }
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 您如何将现有的核心数据iOS 7应用程序的数据迁移到iCloud? - How do you migrate an existing core data iOS 7 app's data into iCloud? 如何创建/使用共享应用程序组容器作为包含应用程序及其在ios中的扩展名之间的缓存存储 - How to create/use a shared app group container as cache storage between containing app and it's extension in ios 如何将核心数据添加到现有的 Xcode 9 Swift 4 iOS 11 项目? - How to add Core Data to existing Xcode 9 Swift 4 iOS 11 project? 如何在 iOS 11+ 中以编程方式打开设置 > 隐私 > 定位服务? - how to programmatically open Settings > Privacy > Location Services in iOS 11+? iOS-如何使用Core Data将2个实体迁移到1个实体? - iOS - How to migrate 2 Entities into 1 using Core Data? 核心数据应用组同步(带扩展) - Core Data App group synchronization (w/ extension) 如何在iOS中将常规应用添加到现有扩展中? - How to add a normal app to an existing extension in iOS? iOS 11+ 的可扩展自定义 UITableViewCell - Expandable custom UITableViewCell for iOS 11+ UITabbar在iOS 10中延伸但不是11+ - UITabbar stretched in iOS 10 but not 11+ 无法设置WKWebView设置Cookie(iOS 11+) - WKWebView setting Cookie not possible (iOS 11+)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM