ios 有什么方法可以预先填充核心数据?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2230354/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:53:19  来源:igfitidea点击:

Any way to pre populate core data?

iosiphoneuitableviewcore-datapersist

提问by Tanner

I've been creating a list app and backing it with core data.

我一直在创建一个列表应用程序并用核心数据支持它。

I would like to have a default list of say 10 airport's items, so that the user doesn't have to start from scratch.

我想要一个包含 10 个机场项目的默认列表,这样用户就不必从头开始。

Is there any way to do this?

有没有办法做到这一点?

Any help is appreciated. Thanks in advance.

任何帮助表示赞赏。提前致谢。

采纳答案by Ken Aspeslagh

Here's the best way (and doesn't require SQL knowledge):
Create a quick Core Data iPhone app (Or even Mac app) using the same object model as your List app. Write a few lines of code to save the default managed objects you want to the store. Then, run that app in the simulator. Now, go to ~/Library/Application Support/iPhone Simulator/User/Applications. Find your application among the GUIDs, then just copy the sqlite store out into your List app's project folder.

这是最好的方法(不需要 SQL 知识):
使用与 List 应用程序相同的对象模型创建一个快速的 Core Data iPhone 应用程序(甚至 Mac 应用程序)。编写几行代码将您想要的默认托管对象保存到商店。然后,在模拟器中运行该应用程序。现在,转到 ~/Library/Application Support/iPhone Simulator/User/Applications。在 GUID 中找到您的应用程序,然后将 sqlite 商店复制到您的 List 应用程序的项目文件夹中。

Then, load that store like they do in the CoreDataBooks example.

然后,像在 CoreDataBooks 示例中那样加载该存储。

回答by Oscar Gomez

Yes there is in fact the CoreDataBooks example does this, you can download the code here: sample code

是的,实际上 CoreDataBooks 示例就是这样做的,您可以在此处下载代码:示例代码

What you do is create the internal store (database) using the normal procedure to initialize your store just like you would with any other store, then you simply run your code and let it execute the code as described in the CoreDataBooks example (code snippet below). Once the store has been initialized you will want to create a NSManagedObjectContextand initialize it with the created persistent store, insert all the entities you need, and save the context.

您所做的是使用正常过程创建内部存储(数据库)以初始化您的存储,就像您使用任何其他存储一样,然后您只需运行代码并让它执行 CoreDataBooks 示例中所述的代码(下面的代码片段) )。存储初始化后,您将需要创建一个NSManagedObjectContext并使用创建的持久存储对其进行初始化,插入您需要的所有实体,并保存上下文。

Once the context has been successfully saved, you can stop your application, then go to finder and go to folder: ~/Library/Developertype in the search .sqlite and look under /Developer, sorting by date will give you the most recent .sqlite database which should match the time that the code was executed, you can then take this store and add it as a resource of your project. This file then can be read by a persistent store coordinator.

成功保存上下文后,您可以停止应用程序,然后转到查找程序并转到文件夹:~/Library/Developer键入搜索 .sqlite 并在 /Developer 下查看,按日期排序将为您提供应匹配的最新 .sqlite 数据库代码执行的时间,然后你可以把这个商店作为你的项目的资源添加。然后,持久存储协调器可以读取该文件。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator) {
    return persistentStoreCoordinator;
}


NSString *storePath = [[self applicationDocumentsDirectory]      stringByAppendingPathComponent: @"CoreDataBooks.sqlite"];
 /*
  Set up the store.
 For the sake of illustration, provide a pre-populated default store.
 */
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
  NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"CoreDataBooks"      ofType:@"sqlite"];
 if (defaultStorePath) {
 [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
 }
}

NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber   numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 
  persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

 NSError *error;
 if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
  // Update to handle the error appropriately.
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
 exit(-1);  // Fail
}    

return persistentStoreCoordinator;
}

Hope that helps.

希望有帮助。

-Oscar

-奥斯卡

回答by gitaarik

With this method you don't need to make a separate app or have any SQL knowledge. You only need to be able to make a JSON file for your initial data.

使用此方法,您无需制作单独的应用程序或具有任何 SQL 知识。您只需要能够为您的初始数据制作一个 JSON 文件。

I use a JSON file that I parse into objects, then insert them in Core Data. I do this when the app initializes. I also make one entity in my core data that indicates if this initial data is already inserted, after I insert the initial data I set this entity so the next time the script runs it sees that the initial data has already been initialized.

我使用解析为对象的 JSON 文件,然后将它们插入到 Core Data 中。我在应用程序初始化时执行此操作。我还在我的核心数据中创建了一个实体,指示是否已经插入了这个初始数据,在我插入初始数据后,我设置了这个实体,以便下次运行脚本时它会看到初始数据已经被初始化。

To read json file into objects:

要将 json 文件读入对象:

NSString *initialDataFile = [[NSBundle mainBundle] pathForResource:@"InitialData" ofType:@"json"];
NSError *readJsonError = nil;
NSArray *initialData = [NSJSONSerialization
                        JSONObjectWithData:[NSData dataWithContentsOfFile:initialDataFile]
                        options:kNilOptions
                        error:&readJsonError];

if(!initialData) {
    NSLog(@"Could not read JSON file: %@", readJsonError);
    abort();
}

Then you can make entity objects for it like this:

然后你可以像这样为它创建实体对象:

[initialData enumerateObjectsUsingBlock:^(id objData, NSUInteger idx, BOOL *stop) {

    MyEntityObject *obj = [NSEntityDescription
                          insertNewObjectForEntityForName:@"MyEntity"
                          inManagedObjectContext:dataController.managedObjectContext];

    obj.name = [objData objectForKey:@"name"];
    obj.description = [objData objectForKey:@"description"];

    // then insert 'obj' into Core Data

}];

If you want a more detailed description on how to do this, check out this tutorial: http://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated

如果您想获得有关如何执行此操作的更详细说明,请查看本教程:http: //www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated

回答by Massimo Cafaro

For 10 items, you can just do this within applicationDidFinishLaunching:in your app delegate.

对于 10 个项目,您可以applicationDidFinishLaunching:在应用程序委托中执行此操作。

Define a method, say insertPredefinedObjects, that creates and populates the instances of the entity in charge of managing your airport items, and save your context. You may either read the attributes from a file or simply hardwire them in your code. Then, call this method inside applicationDidFinishLaunching:.

定义一个方法,例如insertPredefinedObjects,创建和填充负责管理您的机场项目的实体的实例,并保存您的上下文。您可以从文件中读取属性,也可以简单地将它们硬连接到您的代码中。然后,在里面调用这个方法applicationDidFinishLaunching:

回答by Adrian Bigland

Bear in mind, when following the CoreDataBooks example code, that it probably breaks the iOS Data Storage Guidelines:

请记住,在遵循 CoreDataBooks 示例代码时,它可能违反了 iOS 数据存储指南:

https://developer.apple.com/icloud/documentation/data-storage/

https://developer.apple.com/icloud/documentation/data-storage/

I've had an app rejected for copying the (read-only) pre-populated database to the documents directory - as it then gets backed up to iCloud - and Apple only want that to happen to user-generated files.

我有一个应用程序因将(只读)预填充数据库复制到文档目录而被拒绝 - 因为它然后被备份到 iCloud - Apple 只希望这种情况发生在用户生成的文件上。

The guidelines above offer some solutions, but they mostly boil down to:

上述指南提供了一些解决方案,但主要归结为:

  • store the DB in the caches directory, and gracefully handle situations where the OS purges the caches - you will have to rebuild the DB, which probably rules it out for most of us.

  • set a 'do not cache attribute' on the DB file, which is a little arcane, as it needs to be done differently for different OS versions.

  • 将数据库存储在缓存目录中,并优雅地处理操作系统清除缓存的情况 - 您将不得不重建数据库,这对我们大多数人来说可能会排除它。

  • 在 DB 文件上设置一个“不缓存属性”,这有点神秘,因为对于不同的操作系统版本需要以不同的方式完成。

I don't think it is too tricky, but be aware that you have a bit extra to do to make that example code work alongside iCloud...

我不认为这太棘手,但请注意,您需要做一些额外的工作才能使示例代码与 iCloud 一起工作......

回答by Suragch

This answer is only for people who are

这个答案只给有经验的人

  • including a pre-populated database in your app
  • making an app for multiple platforms (iOS, Android, etc.)
  • 在您的应用程序中包含一个预先填充的数据库
  • 为多个平台(iOS、Android 等)制作应用程序

I had made a prepopulated SQLite database for an Android app. Then when I was making an iOS version of the app I thought it would be best to use Core Data. So I spent quite a long time learning Core Data and then rewriting the code to prepopulate the database. Learning how to do every single step in both platforms required lots of research and trial and error. There was a lot less overlap than I would have hoped.

我为 Android 应用程序制作了一个预填充的 SQLite 数据库。然后当我制作应用程序的 iOS 版本时,我认为最好使用 Core Data。所以我花了很长时间学习 Core Data,然后重写代码来预填充数据库。学习如何在两个平台上完成每一步都需要大量的研究和反复试验。重叠比我希望的要少得多。

In the end I just decided to use the same SQLite database from my Android project. Then I used the FMDB wrapper to directly access the database in iOS. The benefits:

最后,我决定使用与我的 Android 项目相同的 SQLite 数据库。然后我使用FMDB包装器直接访问iOS中的数据库。好处:

  • Only need to make the prepopulated database once.
  • Doesn't require a paradigm shift. The syntax between Android and FMDB, while different, is still fairly similar.
  • Have a lot more control over how Queries are performed.
  • Allows full text search.
  • 只需要制作一次预填充数据库。
  • 不需要范式转变。Android 和 FMDB 之间的语法虽然不同,但仍然非常相似。
  • 对查询的执行方式有更多的控制。
  • 允许全文搜索。

Although I don't regret learning Core Data, if I were to do it over I could have saved a lot of time by just sticking to SQLite.

虽然我不后悔学习 Core Data,但如果我重新学习,我本可以通过坚持使用 SQLite 来节省大量时间。

If you are starting in iOS and then planning to move to Android, I would still use a SQLite wrapper like FMDB or some other software to prepopulate the database. Although you can technically extract the SQLite database that you prepopulate with Core Data, the schema (table and column names, etc.) will be strangely named.

如果您从 iOS 开始,然后计划迁移到 Android,我仍然会使用 SQLite 包装器(如 FMDB)或其他一些软件来预填充数据库。尽管您可以从技术上提取预先填充有 Core Data 的 SQLite 数据库,但架构(表和列名称等)的命名会很奇怪。

By the way, if you don't need to modify your prepopulated database, then don't copy it to the documents directory after the app is installed. Just access it directly from the bundle.

顺便说一句,如果您不需要修改您的预填充数据库,那么请不要在安装应用程序后将其复制到文档目录中。只需直接从包中访问它。

// get url reference to databaseName.sqlite in the bundle
let databaseURL: NSURL = NSBundle.mainBundle().URLForResource("databaseName", withExtension: "sqlite")!

// convert the url to a path so that FMDB can use it
let database = FMDatabase(path: databaseURL.path)

This makes it so that you don't have two copies.

这使得你没有两个副本。

Update

更新

I now use SQLite.swiftrather than FMDB, because it integrates better with Swift projects.

我现在使用SQLite.swift而不是 FMDB,因为它可以更好地与 Swift 项目集成。

回答by Felizardo

So I have developed a generic method that loads from a dictionary (possibly from JSON) and populates the database. It should be used ONLY with trusted data (from a safe channel), it can't handle circular references and schema migrations can be problematic... But for simple use cases like mine it should be fine

所以我开发了一种从字典(可能来自 JSON)加载并填充数据库的通用方法。它应该仅用于受信任的数据(来自安全通道),它不能处理循环引用,并且模式迁移可能有问题......但对于像我这样的简单用例应该没问题

Here it goes

来了

- (void)populateDBWithDict:(NSDictionary*)dict
               withContext:(NSManagedObjectContext*)context
{
    for (NSString* entitieName in dict) {

        for (NSDictionary* objDict in dict[entitieName]) {

            NSManagedObject* obj = [NSEntityDescription insertNewObjectForEntityForName:entitieName inManagedObjectContext:context];
            for (NSString* fieldName in objDict) {

                NSString* attName, *relatedClass, *relatedClassKey;

                if ([fieldName rangeOfString:@">"].location == NSNotFound) {
                    //Normal attribute
                    attName = fieldName; relatedClass=nil; relatedClassKey=nil;
                } else {
                    NSArray* strComponents = [fieldName componentsSeparatedByString:@">"];
                    attName = (NSString*)strComponents[0];
                    relatedClass = (NSString*)strComponents[1];
                    relatedClassKey = (NSString*)strComponents[2];
                }
                SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", attName ]);
                NSMethodSignature* signature = [obj methodSignatureForSelector:selector];
                NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
                [invocation setTarget:obj];
                [invocation setSelector:selector];

                //Lets set the argument
                if (relatedClass) {
                    //It is a relationship
                    //Fetch the object
                    NSFetchRequest* query = [NSFetchRequest fetchRequestWithEntityName:relatedClass];
                    query.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:relatedClassKey ascending:YES]];
                    query.predicate = [NSPredicate predicateWithFormat:@"%K = %@", relatedClassKey, objDict[fieldName]];

                    NSError* error = nil;
                    NSArray* matches = [context executeFetchRequest:query error:&error];


                    if ([matches count] == 1) {
                        NSManagedObject* relatedObject = [matches lastObject];
                        [invocation setArgument:&relatedObject atIndex:2];
                    } else {
                        NSLog(@"Error! %@ = %@ (count: %d)", relatedClassKey,objDict[fieldName],[matches count]);
                    }


                } else if ([objDict[fieldName] isKindOfClass:[NSString class]]) {

                    //It is NSString
                    NSString* argument = objDict[fieldName];
                    [invocation setArgument:&argument atIndex:2];
                } else if ([objDict[fieldName] isKindOfClass:[NSNumber class]]) {

                    //It is NSNumber, get the type
                    NSNumber* argument = objDict[fieldName];
                    [invocation setArgument:&argument atIndex:2];

                }
                [invocation invoke];


            }

            NSError *error;
            if (![context save:&error]) {
                NSLog(@"%@",[error description]);
            }
        }
    }   
}

And loads from json...

并从 json 加载...

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"initialDB" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];

NSError* error;
NSDictionary *initialDBDict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                           options:NSJSONReadingMutableContainers error:&error];

[ self populateDBWithDict:initialDBDict withContext: [self managedObjectContext]];

JSON examples

JSON 示例

    {
    "EntitieA": [ {"Att1": 1 }, {"Att1": 2} ],
    "EntitieB": [ {"Easy":"AS ABC", "Aref>EntitieA>Att1": 1} ]
    }

and

{
    "Country": [{"Code": 55, "Name": "Brasil","Acronym": "BR"}],
    "Region": [{"Country>Country>code": 55, "Code": 11, "Name": "Sao Paulo"},
               {"Country>Country>code": 55, "Code": 31, "Name": "Belo Horizonte"}]
}

回答by Md1079

How about check if any objects exist and if not, create one with some data?

如何检查是否存在任何对象,如果不存在,则用一些数据创建一个?

NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Settings"];
_managedObjectSettings = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];

if ([_managedObjectSettings count] == 0) {
    // first time, create some defaults
    NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:@"Settings" inManagedObjectContext:managedObjectContext];

    [newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"speed"];
    [newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"sound"];
    [newDevice setValue:[NSNumber numberWithBool: NO ] forKey:@"aspect"];
    [newDevice setValue:[NSNumber numberWithBool: NO  ] forKey: @"useH264"];
    [newDevice setValue:[NSNumber numberWithBool: NO ] forKey: @"useThumbnail"];

    NSError *error = nil;
    // Save the object to persistent store
    if (![managedObjectContext save:&error]) {
        NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
    }
}

回答by Gaurav Bishnoi

This worked for me. This is a modification of this answerby Andrea Tosoand inspired by this blog. The only issue with the answer is that there is a chance of data loss when moving sqlite files with FileManager. I saved around 500 rows of data by using replacePersistentStore instead of FileManager.default.copyItem

这对我有用。这是这个的修改答案安德烈托索和启发这个博客。答案的唯一问题是使用 FileManager 移动 sqlite 文件时可能会丢失数据。我使用 replacePersistentStore 而不是 FileManager.default.copyItem 保存了大约 500 行数据

Step 1
Populate your Core Data in another app and get files' path using this code:

步骤 1
在另一个应用程序中填充您的核心数据并使用以下代码获取文件的路径:

let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
print(documentsDirectory)

Step2
Drag your 3 files with .sqlite extension into your xCode project. (Be sure to select Add to targets option).

Step2
将您的 3 个扩展名为 .sqlite 的文件拖到您的 xCode 项目中。(一定要选择添加到目标选项)。

Step3
Create function to check app's first run in AppDelegate.swift

Step3
在 AppDelegate.swift 中创建函数来检查应用程序的第一次运行

func isFirstLaunch() -> Bool {
    let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag"
    let isFirstLaunch = !UserDefaults.standard.bool(forKey: hasBeenLaunchedBeforeFlag)
    if (isFirstLaunch) {
        UserDefaults.standard.set(true, forKey: hasBeenLaunchedBeforeFlag)
        UserDefaults.standard.synchronize()
    }
    return isFirstLaunch
}

Step4
Copy this function in AppDelegate.swift to get url where sqlite database should be moved:

Step4
在 AppDelegate.swift 中复制此函数以获取应移动 sqlite 数据库的 url:

func getDocumentsDirectory()-> URL {
    let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory
}

Step 5
Replace declaration of persistentContainer with this one:

步骤 5
用这个替换persistentContainer的声明:

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "ProjectName")

    let storeUrl = self.getDocumentsDirectory().appendingPathComponent("FileName.sqlite")

    if UserDefaults.isFirstLaunch() {
        let seededDataUrl = Bundle.main.url(forResource: "FileName", withExtension: "sqlite")
        try! container.persistentStoreCoordinator.replacePersistentStore(at: storeUrl, destinationOptions: nil, withPersistentStoreFrom: seededDataUrl!, sourceOptions: nil, ofType: NSSQLiteStoreType)
    }

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

回答by simibac

As most answers are quite old, I recommend the following tutorial. It explains how it can be done.

由于大多数答案都很旧,我推荐以下教程。它解释了如何做到这一点。

https://www.youtube.com/watch?v=xcV8Ow9nWFo

https://www.youtube.com/watch?v=xcV8Ow9nWFo