核心数据设计 - 如何同时拥有应用程序数据和用户数据? [英] Core Data Design - How to have both app data and user data?

查看:283
本文介绍了核心数据设计 - 如何同时拥有应用程序数据和用户数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个情况,我有一套基本的信息,我的应用程序。用户可以添加或编辑此信息。但是,有时,我需要能够更新基本数据。我不想触摸用户更改/添加。我看过,以确保在以后的版本中得到修复。获取的属性对于符合数据存储准则至关重要。




应用程序附带一个基础数据集



有许多方法可以处理这个问题:


  1. 从JSON / XML /属性列表/ CSV /等。

  2. 除了您的用户和应用商店之外,以商店作为只读商店开始。

  3. 除了您的用户和应用程序商店之外,还要使用一组预构建的SQLite文件(例如从命令行工具)作为只读存储。



  4. 每个都有优点和缺点:


    1. 这可能很慢,复杂,难以维护,并且容易出错。这是更常见的方法之一做到这一点,和最吸吮的之一。认真。

    2. 记得我刚才说过,一个给定的实体不应该存在于多个商店/配置中吗?嗯,我没有告诉你全部真相。给定实体应仅存在于单个可写入存储中。对于我们的Foo实体,我们可以有一个可写商店,但是我们想要的只有多少只读商店。如果使用选项 NSReadOnlyPersistentStoreOption 将存储添加到持久存储协调器,它将是只读的。这意味着您可以使用一个只读的包含您的starter数据的预构建的NSSQLiteStore,或者您可以更雄心勃勃,使用可以读取其他数据格式的NSAtomicStore或NSIncrementalStore实现。这是最不易出错的解决方案,而且是最容易维护的一种。

    3. 要为此创建一组SQLite文件,您需要构建一个工具,该工具与您的应用程序共享大部分Core Data代码和托管对象模型。

    4. 您可以在存储(例如选项2或3中使用的存储)之间执行迁移,并使用迁移将数据移动到应用程序或用户存储中。迁移通常比执行类似于选项1这样的事情性能更好,虽然它们有一些限制:例如,轻量级迁移可以工作得很好,但不会防止重复或冲突。将需要自定义迁移。这样的迁移看起来像: sqlStore = [persistentStoreCoordinator migratePersistentStore:store toURL:[self applicationStoreURL] options:nil withType:NSSQLiteStoreType error:& error]; 其中 store 是已经添加到协调器的源存储(此操作将删除它,并添加迁移的存储)。这有效地将 store 的内容注入到applicationStoreURL的 NSSQLiteStoreType 商店中。



      1. 应用程序base数据将可由用户编辑。



        这将取决于您的数据设计模型和你想要完成什么。我不能真正地概括这一点。由于应用程序和用户数据存在于不同的存储中,并且由于数据存储指南(及其意图),很难完全按照您的要求进行操作。一个精心设计的数据模型,但是,应该使这很容易。什么是用户改变?你怎么能使这属于他们?这里的一个好例子是favoriting。用户喜爱的内容(产品库存)不应更改,收藏属于用户。他们不会编辑书/音乐/小部件。他们正在创建一个自己和任何标识该内容之间的关系。



        应用程序基本数据需要定期更新,但不能与用户更改冲突。

        h1>

        Annnnd这就是它分开的地方。这是非常困难的,因为以前的要求,我们不太了解数据模型或我们试图完成它。



        一般来说,您不想更新/汇入任何可能与装置上变更的资料相冲突的资料。这里的优点在于,如上所述,由于用户和应用数据之间的干净分离,更改应用数据应该是容易的,因为用户不会改变它。您可以丢弃所有的应用程序数据并替换它,如果您的数据模型设计良好,它只会起作用。



        说你在说的是来自远程源的数据,并将其导入到设备上进行的更改。每隔一段时间你下载一堆JSON与产品更新。解析JSON,创建管理对象,并保存这些对象。你几乎肯定会在某一时刻发生冲突。在对象或属性级别的设备上进行的一些更改将与导入的新信息冲突。您可以使用 NSMergePolicy 来处理冲突,但在此特定情况下,合并策略可能不足。哪一组信息是正确的?设备上的信息,还是远程信息?你是否盲目地接受新数据,或者你必须通过财产检查它的财产吗?人们这样做。我不是在开玩笑。



        用户和应用程序数据的良好分离有助于解决您遇到的(和其他)问题。


        I have a situation where I have a base set of information that ships with my app. The user can add or edit this information. But from time to time, I need to be able to update the base data. I don't want to touch user changes/additions. I've looked at this question, but the analogies used are a bit foreign.

        This must be a common issue, and I would appreciate hearing practices and experiences with this type of co-mingled data sets.

        I was thinking of setting a flag for each record, when the user has modified an object. However, this poses the issue that they may have done so accidentally.

        I could also make two datasets, but that opens up duplication issues.

        So ideas are most welcome.

        解决方案

        So from what I can tell you have 4 requirements (which, really, could be 4 different questions)

        1. Application data and user data should be kept separate.
        2. Application ships with a "base" set of data
        3. Application "base" data will be editable by user.
        4. Application "base" data will need to be periodically updated, but can't conflict with user changes.

        Application data and user data should be kept separate.

        The first requirement, separation of application and user data, relates to the iOS Data Storage Guidelines. Not following the data storage guidelines correctly can lead to an app store rejection (2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected) with a cryptic response pointing you to Technical Q&A 1719. The data storage guidelines exist for several reasons - controlling the size of backups, policies for automatic purging of files when device space is low, and general guidelines for what goes where (developers were getting this wrong).

        Data created or edited by the user belongs under <Application Sandbox>/Documents. This corresponds to NSDocumentDirectory.

        Data that can be regenerated on demand goes under <Application Sanbox>/Library/Caches. This is typically downloaded data or cache files. iOS is in charge of automatically purging anything under this directory. This corresponds to NSCachesDirectory. Data can also be stored in <Application Sandbox>/tmp, though in this case it's recommended that your application periodically purge these files. This corresponds to NSTemporaryDirectory().

        Application data - data that the application needs, and may (or may not) be able to easily recreate - belongs under <Application Sandbox>/Library/Application Support and should also be marked with the "Do not back up" (NSURLIsExcludedFromBackupKey) attribute. When a file is marked with that attribute it will not be backed up AND the system will not purge the data automatically. Because of this, it's recommended that your application at least attempt to control the growth of these files and purge them when not needed. This directory corresponds to NSApplicationSupportDirectory, and the convention is to create a subdirectory with your bundle identifier, and additional subdirectories under that.

        What does this mean for a Core Data application?

        User data and application data should use different store files, in different locations. You will still use a single NSPersistentStoreCoordinator, but add two different persistent stores to it. Your managed object model will need two different configurations - each store gets it's own configuration, and each entity is attached to one of the configurations.

        Configuration:

        This will drive the design of your data model - you should not have a single entity type exist in two different stores, which means you will not be able to have "user edited Foo entity" exist in the user store, while "application provided Foo entity" exists in the application store - unless Foo is abstract, and each store has it's own concrete entity (this is just one possible solution). Cross store relationships can be implemented as fetched properties (more on that in a bit).

        Since Core Data SQLite persistent stores are not single files but collections of files, it's recommended that each store has it's own directory for the store files - this makes saving, deleting, backing up, app updates, etc. much more efficient and reliable. With that in mind, your file structure should look like:

        Application store URL: <Application Sandbox>/Library/Application Support/com.yourcompany.YourApp/ApplicationData/Application.sqlite You will have to create the directories com.yourcompany.YourApp/ApplicationData, and will need to set NSURLIsExcludedFromBackupKey on them.

        User store URL: <Application Sandbox>/Documents/UserData/User.sqlite You will need to create the directory UserData.

        This sounds like a lot of work, but it isn't:

        - (NSURL *)supportFilesDirectoryURL {
            NSURL       *result                         = nil;
            NSURL       *applicationSupportDirectoryURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
            NSString    *bundleName                     = [[NSBundle bundleForClass:[self class]] bundleIdentifier];
        
            result = [applicationSupportDirectoryURL URLByAppendingPathComponent:bundleName isDirectory:@YES];
        
            return result;
        }
        
        - (NSURL *) applicationStoreURL {
            NSError             *error                  = nil;
            NSFileCoordinator   *coordinator            = nil;
            __block BOOL        didCreateDirectory      = NO;
            NSURL               *supportDirectoryURL    = [self supportFilesDirectoryURL];
            NSURL               *storeDirectoryURL      = [supportDirectoryURL URLByAppendingPathComponent:@"ApplicationData" isDirectory:YES];
        
            coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
            [coordinator coordinateWritingItemAtURL:storeDirectoryURL options:NSFileCoordinatorWritingForDeleting error:&error byAccessor:^(NSURL *writingURL){
                NSFileManager   *fileManager    = [[NSFileManager alloc] init];
                NSError         *fileError      = nil;
                if (![fileManager createDirectoryAtURL:writingURL withIntermediateDirectories:YES attributes:nil error:&fileError]){
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        // Handle the error
                    }];
                } else {
                    // Setting NSURLIsExcludedFromBackupKey on the directory will exclude all items in this directory
                    // from backups. It will also prevent them from being purged in low space conditions. Because of this,
                    // the files inside this directory should be purged by the application.
                    [writingURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&fileError];
                    didCreateDirectory = YES;
                }
            }];
        
            // See NSFileCoordinator.h for an explanation.
            if (didCreateDirectory == NO){
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // Handle the error.
                }];
            }
        
            return [NSURL URLWithString:@"Application.sqlite" relativeToURL:storeDirectoryURL ];
        }
        

        You have to add both stores to your persistent store coordinator, each with the correct configuration name as defined in your managed object model:

            if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"Application" URL:[self applicationStoreURL] options:nil error:&error]) {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // Handle the error.
                }];
            }
        
            if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"User" URL:[self userStoreURL] options:nil error:&error]) {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // Handle the error.
                }];
            }
        

        Unfortunately, fetched properties have a problem with nested contexts.

        If the context used is not the root context use of a fetched property will crash - you should duplicate this radar to ensure this gets fixed in a future release. Fetched properties are critical for conforming to the data storage guidelines.

        Application ships with a "base" set of data

        There are a number of ways to handle this:

        1. Create managed objects from JSON/XML/property list/CSV/etc. data and insert into a managed object context and save.
        2. Start with a store as a read only store in addition to your user and application stores.
        3. Start with a set of pre-built SQLite files (such as from a command line tool) as a read only store in addition to your user and application stores.
        4. Start with a pre-built store as in 3, but perform a migration to move those objects into another store.

        Each has advantages and disadvantages:

        1. This can be slow, complicated, difficult to maintain, and error prone. This is one of the more common ways to do this, and one of the most sucky. Seriously. But if this is what you're most comfortable maintaining, more power to you.
        2. Remember when I said earlier that a given entity should not exist in more than one store/configuration? Well, I didn't tell you the whole truth. A given entity should only exist in a single writable store. For our Foo entity, we can have one writable store, but as many read only stores as we want. If the store is added to the persistent store coordintor with the option NSReadOnlyPersistentStoreOption, it will be read-only. This means that you can use a pre-built NSSQLiteStore that is read-only and contains your "starter" data, or you can be more ambitious and use an NSAtomicStore or NSIncrementalStore implementation that can read some other data format. This is the least error prone solution, and one of the easiest to maintain.
        3. To create a set of SQLite files for this you will need to build a tool that shares much of the Core Data code and managed object model as your application. Otherwise this is the same as 3.
        4. You can perform a migration between a store such as one used in options 2 or 3, and use a migration to move data into your application or user store. Migrations are typically much more performant than doing something like option 1, though they have some limitations: for example, a lightweight migration can work very well, but will do nothing to prevent duplicates or conflicts. A custom migration would be required. Such a migration would look like: sqlStore = [persistentStoreCoordinator migratePersistentStore:store toURL:[self applicationStoreURL] options:nil withType:NSSQLiteStoreType error:&error]; where store is the source store already added to the coordinator (this operation will remove it, and add the migrated store). This effectively injects the contents of store into the NSSQLiteStoreType store at applicationStoreURL.

        Application "base" data will be editable by user.

        This will depend very much on the design of your data model and what you're trying to accomplish. I can't really make generalizations about this. Because the application and user data exist in separate stores, and because of the data storage guidelines (and their intent), it's difficult to do exactly what you are asking for. A well designed data model, however, should make this easy. What are the users changing? How can you make that "belong" to them? A good example here is "favoriting". The content the user is favoriting - the product inventory - should not change, and the "favorite" belongs to the user. They're not editing the book/music/widget. They're creating a relationship between themselves and whatever identifies that content. Those kinds of use cases are where fetched properties really shine.

        Application "base" data will need to be periodically updated, but can't conflict with user changes.

        Annnnd this is where it all falls apart. This is pretty difficult because of the previous requirements, and that we don't know much about the data model or what we're trying to accomplish with it.

        In general, you don't want to be updating/importing anything that can conflict with the data being changed on the device. The upside here is that with clean separation between the user and application data as described above, changing application data should be easy because the user won't be changing it. You could throw out all of the application data and replace it, and if you data model is designed well it will just work.

        But let's say you're saying data from a remote source and importing it over changes made on the device. Every once in a while you download a bunch of JSON with product updates. The JSON is parsed, managed objects are created, and those are saved. You're almost certainly going to get conflicts at some point. SOME change made on the device at the object or property level is going to conflict with the new information being imported. You can use an NSMergePolicy to handle the conflicts, but in this specific scenario a merge policy may not be adequate. Which set of information is "correct"? The information on the device, or the remote information? Do you accept the new data blindly, or do you have to check it property by property? People do this. I'm not kidding. You'd think they would have learned by now.

        Good separation of user and application data helps solve this (and other) problems you will run into.

        这篇关于核心数据设计 - 如何同时拥有应用程序数据和用户数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆