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

时间:2010-02-09 15:52:31

标签: ios iphone uitableview core-data persist

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

我希望有一个默认的10个机场项目清单,这样用户就不必从头开始了。

有没有办法做到这一点?

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

11 个答案:

答案 0 :(得分:58)

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

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

答案 1 :(得分:30)

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

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

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

希望有所帮助。

-Oscar

答案 2 :(得分:11)

使用此方法,您无需创建单独的应用程序或具有任何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

答案 3 :(得分:8)

对于10个项目,您可以在应用代表中的applicationDidFinishLaunching:内执行此操作。

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

中调用此方法

答案 4 :(得分:5)

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

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

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

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

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

  • 在数据库文件上设置'不缓存属性',这有点神秘,因为不同的操作系统需要以不同的方式完成。

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

答案 5 :(得分:2)

所以我开发了一个从字典加载的通用方法(可能来自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"}]
}

答案 6 :(得分:2)

如何检查是否存在任何对象,如果没有,请创建一个包含某些数据的对象?

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

答案 7 :(得分:2)

此答案仅适用于

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

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

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

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

虽然我并不后悔学习核心数据,但如果我这样做,我可以通过坚持使用SQLite来节省大量时间。

如果您是从iOS开始然后计划迁移到Android,我仍然会使用像FMDB或其他软件这样的SQLite包装器来预填充数据库。虽然您可以从技术上提取您使用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项目集成。

答案 8 :(得分:0)

另一种存储默认值的方法是通过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"];

答案 9 :(得分:0)

这对我有用。这是answerAndrea Toso的修改,并受此blog的启发。答案的唯一问题是,使用FileManager移动sqlite文件时可能会丢失数据。我使用replacePersistentStore而不是FileManager.default.copyItem

保存了大约500行数据

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

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

第二步
将扩展名为.sqlite的3个文件拖到xCode项目中。 (请确保选择“添加到目标”选项。)

第3步
创建函数以检查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
将此函数复制到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 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
}()

答案 10 :(得分:0)

由于大多数答案都比较老,我建议以下教程。它说明了如何实现。

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