关于Cora Data数据模型的所有事情仍在您脑海中浮现 ,现在是开始使用Core Data的时候了。 在本文中,我们遇到了NSManagedObject
,这是您在使用Core Data时最会与之交互的类。 您将学习如何创建,读取,更新和删除记录。
您还将了解其他一些Core Data类,例如NSFetchRequest
和NSEntityDescription
。 首先让我介绍一下您的新好朋友NSManagedObject
。
先决条件
我在本系列的“核心数据”中介绍的内容适用于iOS 7+和OS X 10.10+,但是重点将放在iOS上。 在本系列中,我将使用Xcode 7.1和Swift 2.1。 如果您更喜欢Objective-C,那么我建议阅读我先前关于Core Data framework的系列文章 。
1.被管理对象
NSManagedObject
实例代表Core Data后备存储中的一条记录。 请记住,后备存储库的外观并不重要。 但是,为了重述数据库的类比, NSManagedObject
实例包含数据库表中一行的信息。
稍后,Core Data使用NSManagedObject
而不是NSObject
作为其对记录进行建模的基类的原因将更加有意义。 在开始使用NSManagedObject
之前,我们需要了解有关此类的一些知识。
NSEntityDescription
每个NSManagedObject
实例都与一个NSEntityDescription
实例关联。 实体描述包括有关托管对象的信息,例如托管对象的实体及其属性和关系 。
NSManagedObjectContext
受管对象还链接到NSManagedObjectContext
的实例。 托管对象所属的托管对象上下文监视托管对象的更改。
2.创建记录
考虑到以上几点,创建托管对象非常简单。 为了确保正确配置了托管对象,建议使用指定的初始化程序来创建新的NSManagedObject
实例。 让我们看看如何通过创建一个新的person对象来工作。
打开上一篇文章中的项目,或从GitHub克隆存储库。 因为我们不会在本文中构建功能性应用程序,所以我们将在应用程序委托类AppDelegate
完成大部分工作。 打开AppDelegate.swift并更新application(_:didFinishLaunchingWithOptions:)
,如下所示。
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Create Managed Object
let entityDescription = NSEntityDescription.entityForName("Person", inManagedObjectContext: self.managedObjectContext)
let newPerson = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
return true
}
我们要做的第一件事是通过调用entityForName(_:inManagedObjectContext:)
创建NSEntityDescription
类的实例。 我们传递要为其创建托管对象的实体的名称"Person"
以及NSManagedObjectContext
实例。
为什么我们需要传递NSManagedObjectContext
对象? 我们指定了要为其创建托管对象的名称,但是我们还需要告诉Core Data在哪里可以找到该实体的数据模型。 请记住,受管对象上下文与持久性存储协调器绑定,并且持久性存储协调器保留对数据模型的引用。 当我们传递一个托管对象上下文时,Core Data向其持久性存储协调器询问其数据模型以找到我们要查找的实体。
在第二步中,我们调用NSManagedObject
类的指定初始化程序init(entity:insertIntoManagedObjectContext:)
。 我们传入实体描述和一个NSManagedObjectContext
实例。 等待? 为什么我们需要传递另一个NSManagedObjectContext
实例? 记住我之前写的内容。 受管对象与实体描述相关联, 并且它位于受管对象上下文中,这就是为什么我们告诉Core Data新管理对象应该链接到哪个受管对象上下文的原因。
这不是太复杂。 是吗? 现在,我们创建了一个新的person对象。 我们如何更改其属性或定义关系? 这是通过利用键值编码来完成的。 要更改我们刚刚创建的新人员对象的名字,请执行以下操作:
// Configure New Person
newPerson.setValue("Bart", forKey: "first")
newPerson.setValue("Jacobs", forKey: "last")
如果您熟悉键值编码,那么应该看起来很熟悉。 因为NSManagedObject
类符合NSKeyValueCoding
协议,所以我们通过调用setValue(_:forKey:)
设置属性。 就这么简单。
这种方法的一个缺点是,您可以通过拼写错误的属性或关系名称来轻松引入错误。 另外,属性名称不会像属性名称那样由Xcode自动完成。 这个问题很容易解决,但这是我们在本系列后面的内容。
在继续探索NSManagedObject
,让我们将newPerson
的age属性设置为44 。
newPerson.setValue(44, forKey: "age")
3.保存记录
即使我们现在有了一个新的人员实例,Core Data仍未将人员保存到其后备存储中。 目前,我们创建的托管对象仅位于其插入的托管对象上下文中。 要将人员对象保存到后备存储,我们需要通过在对象对象上调用save()
保存更改。
save()
方法是一种抛出方法,它返回一个布尔值以指示保存操作的结果。 请看下面的代码块以进行澄清。
do {
try newPerson.managedObjectContext?.save()
} catch {
print(error)
}
生成并运行该应用程序,以查看是否一切正常。 你还撞车了吗? 控制台输出告诉您什么? 它看起来与下面的输出类似吗?
Core Data[8560:265446] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "first"; desired type = NSDate; given type = Swift._NSContiguousString; value = Bart.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010c3f1f45 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010e118deb objc_exception_throw + 48
2 CoreData 0x000000010bf8d840 _PFManagedObject_coerceValueForKeyWithDescription + 2864
3 CoreData 0x000000010bf660d1 _sharedIMPL_setvfk_core + 177
4 Core Data 0x000000010be82200 _TFC9Core_Data11AppDelegate11applicationfS0_FTCSo13UIApplication29didFinishLaunchingWithOptionsGSqGVSs10DictionaryCSo8NSObjectPSs9AnyObject____Sb + 624
5 Core Data 0x000000010be82683 _TToFC9Core_Data11AppDelegate11applicationfS0_FTCSo13UIApplication29didFinishLaunchingWithOptionsGSqGVSs10DictionaryCSo8NSObjectPSs9AnyObject____Sb + 179
6 UIKit 0x000000010cc07034 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 272
7 UIKit 0x000000010cc081da -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 3415
8 UIKit 0x000000010cc0ead3 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1750
9 UIKit 0x000000010cc0bcb3 -[UIApplication workspaceDidEndTransaction:] + 188
10 FrontBoardServices 0x0000000110000784 -[FBSSerialQueue _performNext] + 192
11 FrontBoardServices 0x0000000110000af2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
12 CoreFoundation 0x000000010c31e011 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
13 CoreFoundation 0x000000010c313f3c __CFRunLoopDoSources0 + 556
14 CoreFoundation 0x000000010c3133f3 __CFRunLoopRun + 867
15 CoreFoundation 0x000000010c312e08 CFRunLoopRunSpecific + 488
16 UIKit 0x000000010cc0b605 -[UIApplication _run] + 402
17 UIKit 0x000000010cc1041d UIApplicationMain + 171
18 Core Data 0x000000010be8377d main + 109
19 libdyld.dylib 0x000000010ec3092d start + 1
20 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Xcode告诉我们它期望第一个属性为NSDate
实例,但是我们传入了String
。 如果打开我们在上一篇文章中创建的Core Data模型,您将看到第一个属性的类型确实是Date 。 将其更改为String并再次运行该应用程序。
再次崩溃? 即使这是一个更高级的主题,了解正在发生的事情也很重要。
数据模型兼容性
Xcode控制台中的输出应类似于以下所示的输出。 请注意,该错误与上一个错误不同。 Xcode告诉我们, 开设商店的模型与用于创建商店的模型不兼容 。 这怎么发生的?
Core Data[8879:267986] CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///Users/Bart/Library/Developer/CoreSimulator/Devices/A263775B-4D73-48C8-BD79-825E0BED5128/data/Containers/Data/Application/D7298848-FC36-46EF-8C35-F890F2DB0C89/Documents/SingleViewCoreData.sqlite options:(null) ... returned error Error Domain=NSCocoaErrorDomain Code=134100 "(null)" UserInfo={metadata={
NSPersistenceFrameworkVersion = 640;
NSStoreModelVersionHashes = {
Address = <268460b1 0507da45 f37f8fb5 b17628a9 a56beb9c 8666f029 4276074d 11160d13>;
Person = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "818D6962-8576-4F35-A334-A1A470561950";
"_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {
metadata = {
NSPersistenceFrameworkVersion = 640;
NSStoreModelVersionHashes = {
Address = <268460b1 0507da45 f37f8fb5 b17628a9 a56beb9c 8666f029 4276074d 11160d13>;
Person = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "818D6962-8576-4F35-A334-A1A470561950";
"_NSAutoVacuumLevel" = 2;
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
Core Data[8879:267986] Unresolved error Error Domain=YOUR_ERROR_DOMAIN Code=9999 "Failed to initialize the application's saved data" UserInfo={NSLocalizedDescription=Failed to initialize the application's saved data, NSLocalizedFailureReason=There was an error creating or loading the application's saved data., NSUnderlyingError=0x7fde6d9acc00 {Error Domain=NSCocoaErrorDomain Code=134100 "(null)" UserInfo={metadata={
NSPersistenceFrameworkVersion = 640;
NSStoreModelVersionHashes = {
Address = <268460b1 0507da45 f37f8fb5 b17628a9 a56beb9c 8666f029 4276074d 11160d13>;
Person = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "818D6962-8576-4F35-A334-A1A470561950";
"_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store
不久前,当我们首次启动该应用程序时,Core Data检查了数据模型,并基于该模型为我们创建了一个商店(在这种情况下为SQLite数据库)。 核心数据虽然很聪明。 它确保后备存储的结构和数据模型的结构兼容。 这对于确保我们从后备店中获得期望和最初放置的东西至关重要。
在第一次崩溃期间,我们注意到我们的数据模型包含一个错误,并且将第一个属性的类型从Date更改为String 。 换句话说,即使Core Data已经基于不正确的数据模型为我们创建了后备存储,我们也更改了数据模型。
更新数据模型后,我们再次启动了该应用程序并遇到了第二次崩溃。 Core Data创建Core Data堆栈时要做的一件事就是确保数据模型和后备存储(如果存在)兼容。 在我们的示例中情况并非如此,因此崩溃了。
我们该如何解决? 简单的解决方案是从设备或模拟器中删除该应用程序,然后再次启动该应用程序。 但是,如果您在人们使用的App Store中已经有一个应用程序,则无法执行此操作。 在这种情况下,您将使用迁移,这将在以后的文章中讨论。
因为我们没有数百万用户在使用我们的应用程序,所以我们可以安全地从测试设备中删除该应用程序,然后再次运行它。 如果一切顺利,新人员现在可以安全地存储在商店中,这是为我们创建的SQLite数据库Core Data。
检查后备店
您可以通过查看SQLite数据库来验证保存操作是否有效。 如果您在模拟器中运行应用程序,请导航至/ Users /
打开SQLite数据库并检查名为ZPERSON的表。 该表应该有一条记录,我们在一分钟前插入了一条。
您应该牢记两件事。 首先,无需了解数据库结构。 Core Data为我们管理后备存储,我们不需要了解其结构即可与Core Data一起使用。 其次,切勿直接访问后备存储。 Core Data负责后备存储,如果我们希望Core Data做好其工作,则需要尊重这一点。 如果我们开始与SQLite数据库(或任何其他商店类型)进行交互,则不能保证Core Data将继续正常运行。 简而言之,Core Data负责存储,因此请不要理会它。
4.提取记录
即使我们在下一篇文章NSFetchRequest
仔细研究NSFetchRequest
,我们也需要NSFetchRequest
类向Core Data询问其管理的对象图中的信息。 让我们看看如何使用NSFetchRequest
来获取之前插入的记录。
// Initialize Fetch Request
let fetchRequest = NSFetchRequest()
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Person", inManagedObjectContext: self.managedObjectContext)
// Configure Fetch Request
fetchRequest.entity = entityDescription
do {
let result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
print(result)
} catch {
let fetchError = error as NSError
print(fetchError)
}
初始化获取请求后,我们创建一个NSEntityDescription
对象,并将其分配给获取请求的entity
属性。 如您所见,我们使用NSEntityDescription
类告诉Core Data我们感兴趣的实体。
数据获取由NSManagedObjectContext
类处理。 我们调用executeFetchRequest(_:)
,传入获取请求。 因为executeFetchRequest(_:)
是一个throwing方法,所以我们将方法调用包装在do-catch
语句中。
如果获取请求成功,则该方法返回结果数组。 请注意,即使获取请求成功,或者即使Core Data找不到任何匹配的记录,如果获取请求成功,Core Data也会始终返回一个数组。
运行该应用程序,并在Xcode的控制台中检查输出。 在下面,您可以看到返回的内容,该数组包含一个类型为NSManagedObject
对象。 对象的实体是Person 。
[ (entity: Person; id: 0xd000000000040000 ; data: )]
要访问记录的属性,我们像以前一样使用键值编码。 如果您打算使用Core Data,请务必熟悉键值编码。
do {
let result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
if (result.count > 0) {
let person = result[0] as! NSManagedObject
print("1 - \(person)")
if let first = person.valueForKey("first"), last = person.valueForKey("last") {
print("\(first) \(last)")
}
print("2 - \(person)")
}
} catch {
let fetchError = error as NSError
print(fetchError)
}
您可能想知道为什么我在记录person
名称之前和之后都记录person
对象。 这实际上是本文最重要的课程之一。 看一下下面的输出。
1 - (entity: Person; id: 0xd000000000040000 ; data: )
Bart Jacobs
2 - (entity: Person; id: 0xd000000000040000 ; data: {
addresses = "";
age = 44;
first = Bart;
last = Jacobs;
})
第一次将person
对象记录到控制台时,我们看到数据:
5.断层
构成故障基础的概念并非Core Data独有。 如果您曾经在Ruby on Rails中使用过Active Record ,那么以下内容肯定会引起您的注意。 这个概念并不完全相同,但是从开发人员的角度来看相似。
Core Data试图将其内存占用量保持在尽可能低的水平,而实现该目标所使用的策略之一就是错误 。 不久前,当我们获取Person实体的记录时,Core Data执行了获取请求,但它并未完全初始化代表所获取记录的托管对象。
我们得到的是一个错误,一个代表记录的占位符对象。 该对象的类型为NSManagedObject
,我们可以将其视为此类。 通过不完全初始化记录,Core Data可以保持较低的内存占用量。 在我们的示例中,这并不是节省大量内存,但是想象一下,如果我们获取数十,数百甚至数千条记录会发生什么。
故障通常是您无需担心的。 一旦访问托管对象的属性或关系,就会触发故障,这意味着Core Data将故障变为已实现的托管对象。 您可以在我们的示例中看到这一点,这也是person
对象的第二个log语句不向控制台显示错误的原因。
过错是许多新手的绊脚石,因此,我想确保您了解此概念的基础。 在本系列的后面部分,我们将学习有关故障的更多信息。 如果您想了解有关Core Data故障的更多信息,那么您可能需要阅读有关Core Data故障的深入了解 。
6.更新记录
更新记录就像创建新记录一样简单。 您获取记录,更改属性或关系并保存托管对象上下文。 因为受管对象(记录)链接到受管对象上下文,所以后者知道任何更改,插入,更新和删除。 保存托管对象上下文后,所有内容都会由Core Data传播到后备存储。
看看下面的代码块,在其中我们通过更改人员的年龄并保存更改来更新获取的记录。
let person = result[0] as! NSManagedObject
person.setValue(54, forKey: "age")
do {
try person.managedObjectContext?.save()
} catch {
let saveError = error as NSError
print(saveError)
}
您可以像以前一样通过再次查看SQLite存储来验证更新是否成功。
7.删除记录
删除记录遵循相同的模式。 我们通过调用deleteObject(_:)
并传递需要删除的托管对象来告诉托管对象上下文需要从持久性存储中删除一条记录。
在我们的项目中,通过将先前获取的人员对象传递到托管对象上下文的deleteObject(_:)
方法中,将其删除。 请注意,直到我们在托管对象上下文上调用save()
为止,删除操作才提交给后备存储。
let person = result[0] as! NSManagedObject
self.managedObjectContext.deleteObject(person)
do {
try self.managedObjectContext.save()
} catch {
let saveError = error as NSError
print(saveError)
}
您可以通过再次查看SQLite存储来验证删除操作是否成功。
结论
在本教程中,我们不仅介绍了创建,获取,更新和删除记录的内容,还介绍了更多内容。 我们已经介绍了Core Data所依赖的一些重要概念,例如故障和数据模型兼容性。
在本系列的下一部分中,您将学习如何创建和更新关系,我们将对NSFetchRequest
类进行深入研究。 我们还将开始使用NSPredicate
和NSSortDescriptor
来使获取请求变得灵活,动态和强大。
翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-managed-objects-and-fetch-requests--cms-25068