核心数据和Swift:托管对象和提取请求

关于Cora Data数据模型的所有事情仍在您脑海中浮现 ,现在是开始使用Core Data的时候了。 在本文中,我们遇到了NSManagedObject ,这是您在使用Core Data时最会与之交互的类。 您将学习如何创建,读取,更新和删除记录。

您还将了解其他一些Core Data类,例如NSFetchRequestNSEntityDescription 。 首先让我介绍一下您的新好朋友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 ,让我们将newPersonage属性设置为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 = <c9bed257 c4bca383 38cd682a 227f38a8 c1a5bb27 fb02932c 42c62714 47463637>;
    };
    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 = <c9bed257 c4bca383 38cd682a 227f38a8 c1a5bb27 fb02932c 42c62714 47463637>;
        };
        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 = <c9bed257 c4bca383 38cd682a 227f38a8 c1a5bb27 fb02932c 42c62714 47463637>;
    };
    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 / <USER> / Library / Developer / CoreSimulator / Devices / <DEVICE_ID> / data / Containers / Data / Application / <APPLICATION_ID> / Documents / SingleViewCoreData .sqlite 。 由于应用程序数据的位置随Xcode的每个发行版而变化,因此上述路径仅对Xcode 7有效。

打开SQLite数据库并检查名为ZPERSON的表。 该表应该有一条记录,我们在一分钟前插入了一条。

SQLite数据库的内容

您应该牢记两件事。 首先,无需了解数据库结构。 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

[<NSManagedObject: 0x7fab71e0cee0> (entity: Person; id: 0xd000000000040000 <x-coredata://E9E9FE9D-D000-4F1D-BF2C-F37CEDF5FC39/Person/p1> ; data: <fault>)]

要访问记录的属性,我们像以前一样使用键值编码。 如果您打算使用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 - <NSManagedObject: 0x7f930b924210> (entity: Person; id: 0xd000000000040000 <x-coredata://E9E9FE9D-D000-4F1D-BF2C-F37CEDF5FC39/Person/p1> ; data: <fault>)
Bart Jacobs
2 - <NSManagedObject: 0x7f930b924210> (entity: Person; id: 0xd000000000040000 <x-coredata://E9E9FE9D-D000-4F1D-BF2C-F37CEDF5FC39/Person/p1> ; data: {
    addresses = "<relationship fault: 0x7f930b924150 'addresses'>";
    age = 44;
    first = Bart;
    last = Jacobs;
})

第一次将person对象记录到控制台时,我们看到数据:<fault> 。 但是, 数据第二次包含对象属性和关系的内容。 这是为什么? 这与故障 (Core Data的关键概念)有关。

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类进行深入研究。 我们还将开始使用NSPredicateNSSortDescriptor来使获取请求变得灵活,动态和强大。

翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-managed-objects-and-fetch-requests--cms-25068

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值