iOS Apprentice中文版-从0开始学iOS开发-第四十一课

使用Core Data存储位置信息

此刻你已经拥有了一个app,可以获得用户当前的GPS坐标。它还有一个界面可以用来标记位置信息,可以添加一段描述并且选择一个分类。稍后,你将添加照片功能进去。

接下来我们要把用户的位置信息保存到一个列表里去。

这个界面完成后会是下面这个样子:

你需要用某种方法把获取到的位置信息保存下来。

上一次你保存信息的时候,你创建了符合NSCoding协议的数据模型对象,并且使用NSKeyedArchiver将它们保存到.plist文件中。这样确实可以达到很好的效果,但是这节课,我会给你介绍一个新的框架,它可以帮你节省很多时间,那就是Core Data。

Core Data是用于iOS应用的一个持久化存储对象。如果你之前自己看过关于Core Data的文档的话,你也许发现它很难,但是实际上它的原理非常简单。

你之前学到过假如一个对象没有其他对象引用它的时候,这个对象会被销毁掉。此外,所有对象都会在app被关闭后销毁掉。

通过使用Core Data,你可以指定一些对象持久化存储,它们会被保存到数据商店中。这样即使所有引用它们的对象以及实例都被销毁了,它的数据还是安全的存储在Core Data中,并且你可以随时把它取回来。

如果你之前学习过数据库的话,你会发现它们很像,只是Core Data保存的对象,而数据库保存的是关系表。

添加Core Data到app中。

Core Data需要使用数据模型。这是一个特殊的文件用于表述你要持久存储的对象。与常规对象不同,这些托管对象会将数据保存起来,除非你本人要求删除它们。

添加一个新的文件到工程中,在文件模版处选择Core Data分节下的Data Model文件类型:

将文件命名为DataModel。

此时你可以在工程导航器中看到一个DataModel.xcdatamodeld文件。

选定这个文件,就可以打开数据模型编辑器了:

你用Core Data管理的每一个对象,都要先为它创建一个实体(entity)。

一个实体描述你的对象将拥有哪些数据字段。 从某种意义上来说,它的作用与一个类相同,但是是专门用于Core Data的数据存储。(如果你以前学习过数据库的话,你可以把一个实体当作一个表)

我们的这个app仅有一个实体,就是位置信息,每个位置信息包含以下数据:

1、经纬度

2、街道信息

3、时间

4、用户添加的描述

5、分类

这些都是Tag Location界面上的内容,照片除外。 照片可能会非常大,它可能需要几兆字节的存储空间。 虽然Core Data存储可以处理大量的“blobs(不知道啥意思)”数据,但是最好将照片作为单独的文件存储在应用程序的Documents目录中,后续我们会单独讲解关于照片存储的内容。

点击数据模型编辑器底部的Add Entity按钮。这样就会在ENTITIES标签下新增一个实体。将其命名为Location。在右边的面板中单击一下就可以重命名了,见下图蓝色被选定的那个记录:

在实体的内部有三个分节,Attributes,Relationships以及Fetched Properties。Attributes就是实体的数据字段。

这个app仅有一个实体,但是通常app会有多个实体并且相互关联。通过Relationships与Fetched Properties你可以告诉Core Data你的对象间的依赖关系。

对于我们这个app而言,只需要使用Attributes。

点击编辑器底部的Add Attribute按钮,或者Attributes分节下的小加号按钮添加新的属性。给它命名为latitude,并且将类型设置为Double:

Attributes大体上盒实例变量很像,因此它也有类型的概念。之前我们介绍过latitude和longitude都是Double型的,所以它们对应的Attributes也应该是Double型的。

⚠️:不要让这些术语把你吓着,你可以这样想:
entity = object or class
attribute = variable
如果你想知道方法在Core Data中对应什么,我可以告诉你不存在这种概念。Core Data仅用于存储对象的数据部分。实体的概念是这样的:对象的数据,对象间的关系(如果存在的话)。
稍后,你将创建一个Swift文件来定义自己的Location类。 它将与数据模型中的Location实体关联。 但它仍然是一个普通的类,所以你可以添加你自己的方法。

添加剩余的attributes到Location实体中:

longitude,类型Double

date,类型Date

locationDescription,类型String

category,类型String

placemark,类型Transformable

现在数据模型看起来应该是这个样子:

这里提一下locationDescription,你不能将这个字段命名为description,因为description是NSObject中的一个对象。如果你这样做了的话,Xcode会毫不客气的给出一个报错。

placemark的类型是Transformable。Core Data仅支持比较有限的类型,比如String、Double、Date。但是placemark的类型是CLPlacemark,Core Data并不能直接支持这种类型。

幸运的是,Core Data有一种处理任意数据的规则。任何遵循NSCoding协议的类可以被存储为Transformable类型。更加幸运的是CLPlacemark正好遵循NSCoding协议,所以你可以直接存储它,不用做什么额外的工作。

默认情况,实体的属性都是可选型,意味着它们可以为nil。在我们的app中只有placemark可能为nil,如果地址解析失败的话。所以我们可以在Xcode中指明这一点(其实不做也无所谓)。

选择category属性。在属性面板中,取消选中Optional选项,见下图:

然后用同样方法,把除了placemark以外的所有属性的Optional选项都取消选中。

然后使用command+S保存一下,虽然Xcode支持自动保存,但是我们最好还是养成自己随时保存的习惯。

数据模型基本处理完了,但是还有一件事我要特别讲一下。

点击选定Location实体,然后打开属性面板。

你可以看到Class这个字段中填写的是“NSManagedObject”。当你从Core Data中取回这个实体时,你会得到一个NSManagedObject类的对象。

这是Core Data管理的所有的对象的基础类。常规对象从NSObject继承,但来自Core Data的对象是由NSManagedObject扩展来的。

因为直接使用NSManagedObject是有点限制的,所以你可以使用自己的类来代替。 虽然并不是必须要这样做,但是这样确实会使Core Data的使用简单一些。

现在我们要做的是,从数据存储中取回Location实体时,直接返回一个Location的实例,而不是NSManagedObject。

首先在属性面板中将Codegen这一栏中的内容选择为Manual/None。

⚠️:从版本8开始,Xcode可以从数据模型自动生成实体类的源代码。 Codegen设置决定了它如何做到这一点。 为了本教程的目的,我们暂时不使用自动代码生成,这就是你将Codegen设置为Manual / None的原因。 了解如何制作自己的NSManagedObject子类,而不是完全依靠Xcode是很有用的。

即使你不使用自动类生成,Xcode仍然可以提供帮助。

选择菜单Editor → Create NSManagedObject Subclass。

然后会弹出一个窗口让你选择实体和数据模型。

选择DataModel,然后点击Next。然后选择Location再点击Next。

然后选择保存位置,点击Create结束。

现在两个新的文件被添加到工程中了。第一个是Location+CoreDataClass.swift,它里面的内容是这样的:

import Foundation
import CoreData
public class Location: NSManagedObject {
}

和你看到的一样,Location继承了NSManagedObject,而不是新建了一个NSObject。

第二个文件是Location+CoreDataProperties.swift:

import Foundation
import CoreData


extension Location {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Location> {
        return NSFetchRequest<Location>(entityName: "Location")
    }

    @NSManaged public var latitude: Double
    @NSManaged public var longitude: Double
    @NSManaged public var date: NSDate?
    @NSManaged public var locationDescription: String?
    @NSManaged public var category: String?
    @NSManaged public var placemark: NSObject?

}

在这个文件中,Xcode创建了与实体中的属性相对应的实例。这里你没有见过的东西是extension关键字。

通过extension(扩展),你可以将其他功能添加到现有对象,而无需更改该对象的源代码。 这甚至适用于你实际上没有这些对象的源代码。 稍后在本教程中,您将看到一个如何使用扩展向iOS框架中的对象添加新方法的示例。

在这里,扩展名被用于其他目的。 如果稍后更改了Core Data模型,并且想要自动更新代码以匹配这些更改,则可以再次选择“创建NSManagedObject子类”,而Xcode将只覆盖“Location + CoreDataProperties.swift”中的内容,但不会添加任何内容到Location + CoreDataClass.swift。

因此,如果你打算在之后覆盖此文件,则更改Location + CoreDataProperties.swift并不是一个好主意。 不幸的是,Xcode在属性的类型上有所不满,所以你不得不对这个文件做一些修改。

首先要解决的是placemark变量。 因为你创建了一个Transformable类型的placemark,所以Xcode并不知道这是什么类型的对象,所以它选择了泛型类型NSObject。但是你知道这将是一个CLPlacemark对象,所以你可以做些改变来让事情变得更容易。

首先在Location+CoreDataProperties.swift中导入CoreLocation框架:

import CoreLocation

然后将placemark属性修改为:

@NSManaged var placemark: CLPlacemark?

问号不能去掉,因为placemark是个可选型。

同时将date属性的类型修改为Date:

@NSManaged var date: Date

NSDate是OC中的用法,在Swift中,我们使用Date。并且去掉问号,它不再是一个可选型。

最后,删除category和locationDescription属性后面的问号。 之前你告诉Core Data这些属性不是可选项,所以他们不需要这个问号。

由于这是一个托管对象,并且数据存在于数据存储区内,因此Swift将以特殊方式处理Location的变量。 @NSManaged关键字告诉编译器这些属性将在运行时由Core Data解析。 当你为这些属性添加一个新的值的时候,Core Data会把这个值放到数据存储中以保存,而不是放在一个普通的实例变量中。

这样就结束了这个app的数据模型的定义。 现在你必须把它连接到一个数据存储。

数据存储

在iOS系统中,Core Data将其所有数据存储到SQLite数据库(发音为“SQL light”)。 如果你不知道SQLite是什么,那也行。 稍后你将看到该数据库,但是你并不需要知道数据存储中为了使用Core Data而做了些了什么。

但是,当应用程序启动时,你必须初始化这些数据存储。 对于使用Core Data的app来说,这个代码是相同的,并且它在app的委托类中。

app委托(app delegate)是获取与app有关的通知的对象。 例如,这是iOS通知应用程序启动的地方。

你会在AppDelegate中做些修改。

打开AppDelegate.swift并且导入Core Data框架(在顶部第一行):

import CoreData

在AppDelegate类的内部添加以下代码:

lazy var persistentContainer: NSPersistentContainer = {
  let container = NSPersistentContainer(name: "DataModel")
  container.loadPersistentStores(completionHandler: {
    storeDescription, error in
    if let error = error {
      fatalError("Could load data store: \(error)")
    }
})
  return container
}()

这是就是你需要加载之前定义的数据模型的代码,并将其连接到SQLite数据存储。

这里的目标是创建一个所谓的NSManagedObjectContext对象。 这是你将用于与CoreData交谈的对象。 为了获得NSManagedObjectContext对象,应用程序需要做几件事情:

1、从你之前创建的CoreDatamodel中创建一个NSManagedObjectModel。 该对象表示运行时的数据模型。 你可以从其中得到类型的实体,这些实体有什么属性,等等。 在大多数app中,你不需要直接使用NSManagedObjectModel对象。

2、创建一个NSPersistentStoreCoordinator对象。该对象负责SQLite数据库。

3、最后,创建NSManagedObjectContext对象并将其连接到持久存储协调器。

这些对象一起被称为“Core Data stack核心数据栈”。

在iOS 9中,你必须手动执行这些步骤,这可能会有点混乱。 幸运的是,iOS 10中有一个新的对象NSPersistentContainer,它负责处理所有事情。

这并不意味着你应该立即忘记你刚才了解到的NSManagedObjectModel和NSPersistentStoreCoordinator的内容,但是它可以帮你避免编写一堆代码。

刚刚添加的代码将创建一个类型为NSPersistentContainer的实例变量persistentContainer。 为了得到我们之后的NSManagedObjectContext,你可以简单的访问一下persistentContainer的viewContext属性。

为了方便起见,添加一个新属性来从持久容器中获取NSManagedObjectContext:

 lazy var managedObjectContext: NSManagedObjectContext = self.persistentContainer.viewContext

现在,我们已经做好使用CoreData的准备工作了。

编译一下app确保没有错误。 如果你运行app,你不会发现任何区别,因为你实际上还没有使用Core Data。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容