【问题标题】:Any way to pre populate core data?有什么方法可以预填充核心数据?
【发布时间】:2011-01-14 21:06:53
【问题描述】:

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

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

有没有办法做到这一点?

感谢任何帮助。 提前致谢。

【问题讨论】:

标签: ios iphone uitableview core-data persist


【解决方案1】:

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

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

【讨论】:

  • 如果 Apple 决定在不同 iOS 版本之间更改 Core Data 的内部结构(并且您没有及时发布更新),这不会破坏您的应用程序吗?
  • 我真的怀疑 Apple 会做出改变,破坏其读取自己的数据库文件的能力。
  • Apple 可以在系统升级期间迁移设备上所有现有的核心数据数据库,因此它仍然可以读取它们。但是这样的迁移可能会跳过新安装中的预打包数据库文件。
  • 那根本行不通。应用数据可在用户电脑iTunes中备份,随时恢复。
  • 我按照你的建议做了,但我仍然得到“用于打开商店的模型与用于创建商店的模型不兼容”。我实际上将模型文件从一个项目复制到另一个项目......所以我很确定它们是相同的。
【解决方案2】:

是的,事实上 CoreDataBooks 的例子就是这样做的,你可以在这里下载代码:sample code

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

成功保存上下文后,您可以停止应用程序,然后转到 finder 并转到文件夹:~/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;
}

希望对您有所帮助。

-奥斯卡

【讨论】:

  • 不,你不是我创建了我的,就像你使用 SQL Server 2005 创建它并简单地插入值,使用 SQLite 数据库浏览器,你可以在这里获得:mac.softpedia.com/get/Developer-Tools/…
  • 我相信我会更喜欢 sql 浏览器方法,因为我可以添加不同的列表。我已经下载了。我只是添加一个项目,将其命名为护照,然后再添加 9 个项目,然后我就完成了吗?
  • 是的,它和其他数据库浏览器一样易于使用。
  • 这个答案具有误导性。您不能只是将数据转储到任何旧的 SQLite 数据库中,然后将其加载到 Core Data 中。 Core Data 的 SQLite 数据库有一个非常具体的内部结构,没有记录,建议您不要手动写入。
  • 有人能把它翻译成 Swift 吗?那会有很大帮助。
【解决方案3】:

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

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

将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();
}

然后你可以像这样为它制作实体对象:

[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

}];

如果您想更详细地了解如何执行此操作,请查看本教程: http://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated

【讨论】:

    【解决方案4】:

    对于 10 个项目,您只需在应用委托中的 applicationDidFinishLaunching: 内执行此操作即可。

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

    【讨论】:

      【解决方案5】:

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

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

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

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

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

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

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

      【讨论】:

      【解决方案6】:

      这对我有用。这是Andrea Tosoanswer 的修改,并受到blog 的启发。答案的唯一问题是使用 FileManager 移动 sqlite 文件时可能会丢失数据。我使用 replacePersistentStore 而不是 FileManager.default.copyItem 保存了大约 500 行数据

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

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

      第二步
      将您的 3 个带有 .sqlite 扩展名的文件拖到您的 xCode 项目中。 (请务必选择添加到目标选项)。

      第三步
      在 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
      }
      

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

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

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

      // MARK: - Core Data stack
      
      lazy var persistentContainer: NSPersistentContainer = {
          let container = NSPersistentContainer(name: "ProjectName")
      
          let storeUrl = self.getDocumentsDirectory().appendingPathComponent("FileName.sqlite")
      
          if 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
      }()
      

      【讨论】:

        【解决方案7】:

        此答案仅适用于以下人员

        • 在您的应用中包含一个预填充的数据库
        • 为多个平台(iOS、Android 等)制作应用程序

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

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

        • 只需制作一次预填充数据库。
        • 不需要范式转换。 Android 和 FMDB 之间的语法虽然不同,但仍然非常相似。
        • 对查询的执行方式有更多的控制。
        • 允许全文搜索。

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

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

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

        // 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)
        

        这样你就没有两个副本了。

        更新

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

        【讨论】:

        • SQLite.swift 很神奇。说@Suragch,你可能知道......在Android中,你有SQLiteOpenHelper,它有方便的“OnUpgrade”概念。处理应用程序何时升级(即来自应用商店的新版本)。你碰巧知道,这个问题在 iOS 中是如何处理的,它会成为一个问题吗?干杯
        • 顺便说一句,你上面说的太真实了。如果你正在做 ios-android 开发,那么简单地坚持使用 SQLite 会好得多。就算是没用过sql的人,也只是学了几句简单的sql语句而已。
        • @JoeBlow,我还没有在 Sqlite.swift 中进行模式更新。我想我记得在文档中读过一些关于它的内容,但我现在只能看到this。它似乎不像 OnUpgrade 在 Android 中那样内置。
        • 感谢您的回复;对,看到了“迁移和架构”部分——也许这就是它的大小。谢谢。顺便说一句,我问了一个关于这个问题的问题,stackoverflow.com/questions/41100001/… Cheers!!
        【解决方案8】:

        所以我开发了一个从字典(可能从 JSON)加载并填充数据库的通用方法。 它只能与受信任的数据(来自安全通道)一起使用,它不能处理循环引用并且架构迁移可能会出现问题......但对于像我这样的简单用例来说应该没问题

        来了

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

        并从 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 示例

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

        {
            "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"}]
        }
        

        【讨论】:

          【解决方案9】:

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

          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]);
              }
          }
          

          【讨论】:

            【解决方案10】:

            另一种存储默认值的方法是通过 NSUserDefaults 找到的。 (惊喜!) 而且很简单。

            有人建议,把它放到applicationDidFinishLaunching

            在给定 10 个默认值的情况下,Airport0 到 9

            设置

            NSUserDefaults *nud = [NSUserDefaults standardUserDefaults];
            [nud setString:@"MACADDRESSORWHY" forKey:@"Airport0"];
                ...
            [nud setString:@"MACADDRESSORWHY" forKey:@"Airport9"];
            [nud synchronize];
            

            [[NSUserDefaults standardUserDefaults] setString:@"MACADDRESSORWHY" forKey:@"Airport9"]];
                 ...
            [[NSUserDefaults standardUserDefaults] synchronize];
            

            然后,获取默认值。

            NSString *air0 = [[NSUserDefaults standardUserDefaults] stringForKey:@"Airport0"];
            

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-01-10
                • 2011-04-27
                • 2012-01-10
                • 2011-12-23
                • 2011-07-03
                相关资源
                最近更新 更多