有办法预填充核心数据吗?

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

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

有什么办法吗?

感谢你的帮助。 先谢谢你。

42815 次浏览

是的,实际上 CoreDataBooks 示例做到了这一点,您可以在这里下载代码: 示例代码

您所要做的就是使用常规过程创建内部存储(数据库)来初始化您的存储,就像对待其他存储一样,然后您只需运行您的代码并让它执行 CoreDataBooks 示例中描述的代码(下面的代码片段)。初始化存储之后,您将希望创建一个 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;
}

希望能帮上忙。

奥斯卡

对于10个项目,您可以只在 applicationDidFinishLaunching:内在您的应用程序委托这样做。

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

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

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

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

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

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

上面的指导方针提供了一些解决方案,但它们大多归结为:

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

  • 在数据库文件上设置一个“不缓存属性”,这有点神秘,因为对于不同的操作系统版本它需要采用不同的方式。

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

另一种存储默认值的方法是通过 NSUserDefault 找到的 而且很简单。

有人建议,把这个放进 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"];

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

因此,我开发了一个通用方法,它从字典(可能从 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"}]
}

如何检查是否有任何对象存在,如果没有,创建一个与一些数据?

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

这个答案只适用于那些

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

我为一个 Android 应用程序做了一个预填充的 SQLite 数据库。然后,当我正在制作一个 iOS 版本的应用程序,我认为它是最好的使用核心数据。因此,我花了相当长的时间学习核心数据,然后重写代码,以预填充数据库。学习如何在这两个平台上完成每一个步骤需要大量的研究和尝试。重叠的部分比我想象的要少得多。

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

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

虽然我并不后悔学习核心数据,但是如果我重新学习的话,只要坚持使用 SQLite 就可以节省很多时间。

如果您刚开始使用 iOS,然后计划迁移到 Android,我仍然会使用像 FMDB 或其他软件这样的 SQLite 包装器来预填充数据库。虽然您可以从技术上提取使用 Core Data 预填充的 SQLite 数据库,但是模式(表和列名等)的名称会很奇怪。

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

// 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 项目集成。

这招对我很管用。这是 Andrea Toso回答的修改,并受到 博客的启发。唯一的问题是,当使用 FileManager 移动 sqlite 文件时,有可能丢失数据。通过使用 replacePersisentStore 而不是 FileManager.default.copItem,我保存了大约500行数据

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

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

第二步
将扩展名为.sqlite 的3个文件拖到 xCode 项目中(确保选择 Add to target 选项)。

第三步
创建检查应用程序第一次运行的函数

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
}

第四步
将此函数复制到 AppDeliate.swift 中,以获取应该移动 sqlite 数据库的 URL:

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

第五步
用下面这个代码替换 everentContainer 的声明:

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

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

Https://www.youtube.com/watch?v=xcv8ow9nwfo