iOS Apprentice中文版-從0開(kāi)始學(xué)iOS開(kāi)發(fā)-第四十一課

使用Core Data存儲(chǔ)位置信息

此刻你已經(jīng)擁有了一個(gè)app,可以獲得用戶當(dāng)前的GPS坐標(biāo)。它還有一個(gè)界面可以用來(lái)標(biāo)記位置信息,可以添加一段描述并且選擇一個(gè)分類。稍后,你將添加照片功能進(jìn)去。

接下來(lái)我們要把用戶的位置信息保存到一個(gè)列表里去。

這個(gè)界面完成后會(huì)是下面這個(gè)樣子:

你需要用某種方法把獲取到的位置信息保存下來(lái)。

上一次你保存信息的時(shí)候,你創(chuàng)建了符合NSCoding協(xié)議的數(shù)據(jù)模型對(duì)象,并且使用NSKeyedArchiver將它們保存到.plist文件中。這樣確實(shí)可以達(dá)到很好的效果,但是這節(jié)課,我會(huì)給你介紹一個(gè)新的框架,它可以幫你節(jié)省很多時(shí)間,那就是Core Data。

Core Data是用于iOS應(yīng)用的一個(gè)持久化存儲(chǔ)對(duì)象。如果你之前自己看過(guò)關(guān)于Core Data的文檔的話,你也許發(fā)現(xiàn)它很難,但是實(shí)際上它的原理非常簡(jiǎn)單。

你之前學(xué)到過(guò)假如一個(gè)對(duì)象沒(méi)有其他對(duì)象引用它的時(shí)候,這個(gè)對(duì)象會(huì)被銷毀掉。此外,所有對(duì)象都會(huì)在app被關(guān)閉后銷毀掉。

通過(guò)使用Core Data,你可以指定一些對(duì)象持久化存儲(chǔ),它們會(huì)被保存到數(shù)據(jù)商店中。這樣即使所有引用它們的對(duì)象以及實(shí)例都被銷毀了,它的數(shù)據(jù)還是安全的存儲(chǔ)在Core Data中,并且你可以隨時(shí)把它取回來(lái)。

如果你之前學(xué)習(xí)過(guò)數(shù)據(jù)庫(kù)的話,你會(huì)發(fā)現(xiàn)它們很像,只是Core Data保存的對(duì)象,而數(shù)據(jù)庫(kù)保存的是關(guān)系表。

添加Core Data到app中。

Core Data需要使用數(shù)據(jù)模型。這是一個(gè)特殊的文件用于表述你要持久存儲(chǔ)的對(duì)象。與常規(guī)對(duì)象不同,這些托管對(duì)象會(huì)將數(shù)據(jù)保存起來(lái),除非你本人要求刪除它們。

添加一個(gè)新的文件到工程中,在文件模版處選擇Core Data分節(jié)下的Data Model文件類型:

將文件命名為DataModel。

此時(shí)你可以在工程導(dǎo)航器中看到一個(gè)DataModel.xcdatamodeld文件。

選定這個(gè)文件,就可以打開(kāi)數(shù)據(jù)模型編輯器了:

你用Core Data管理的每一個(gè)對(duì)象,都要先為它創(chuàng)建一個(gè)實(shí)體(entity)。

一個(gè)實(shí)體描述你的對(duì)象將擁有哪些數(shù)據(jù)字段。 從某種意義上來(lái)說(shuō),它的作用與一個(gè)類相同,但是是專門(mén)用于Core Data的數(shù)據(jù)存儲(chǔ)。(如果你以前學(xué)習(xí)過(guò)數(shù)據(jù)庫(kù)的話,你可以把一個(gè)實(shí)體當(dāng)作一個(gè)表)

我們的這個(gè)app僅有一個(gè)實(shí)體,就是位置信息,每個(gè)位置信息包含以下數(shù)據(jù):

1、經(jīng)緯度

2、街道信息

3、時(shí)間

4、用戶添加的描述

5、分類

這些都是Tag Location界面上的內(nèi)容,照片除外。 照片可能會(huì)非常大,它可能需要幾兆字節(jié)的存儲(chǔ)空間。 雖然Core Data存儲(chǔ)可以處理大量的“blobs(不知道啥意思)”數(shù)據(jù),但是最好將照片作為單獨(dú)的文件存儲(chǔ)在應(yīng)用程序的Documents目錄中,后續(xù)我們會(huì)單獨(dú)講解關(guān)于照片存儲(chǔ)的內(nèi)容。

點(diǎn)擊數(shù)據(jù)模型編輯器底部的Add Entity按鈕。這樣就會(huì)在ENTITIES標(biāo)簽下新增一個(gè)實(shí)體。將其命名為L(zhǎng)ocation。在右邊的面板中單擊一下就可以重命名了,見(jiàn)下圖藍(lán)色被選定的那個(gè)記錄:

在實(shí)體的內(nèi)部有三個(gè)分節(jié),Attributes,Relationships以及Fetched Properties。Attributes就是實(shí)體的數(shù)據(jù)字段。

這個(gè)app僅有一個(gè)實(shí)體,但是通常app會(huì)有多個(gè)實(shí)體并且相互關(guān)聯(lián)。通過(guò)Relationships與Fetched Properties你可以告訴Core Data你的對(duì)象間的依賴關(guān)系。

對(duì)于我們這個(gè)app而言,只需要使用Attributes。

點(diǎn)擊編輯器底部的Add Attribute按鈕,或者Attributes分節(jié)下的小加號(hào)按鈕添加新的屬性。給它命名為latitude,并且將類型設(shè)置為Double:

Attributes大體上盒實(shí)例變量很像,因此它也有類型的概念。之前我們介紹過(guò)latitude和longitude都是Double型的,所以它們對(duì)應(yīng)的Attributes也應(yīng)該是Double型的。

??:不要讓這些術(shù)語(yǔ)把你嚇著,你可以這樣想:
entity = object or class
attribute = variable
如果你想知道方法在Core Data中對(duì)應(yīng)什么,我可以告訴你不存在這種概念。Core Data僅用于存儲(chǔ)對(duì)象的數(shù)據(jù)部分。實(shí)體的概念是這樣的:對(duì)象的數(shù)據(jù),對(duì)象間的關(guān)系(如果存在的話)。
稍后,你將創(chuàng)建一個(gè)Swift文件來(lái)定義自己的Location類。 它將與數(shù)據(jù)模型中的Location實(shí)體關(guān)聯(lián)。 但它仍然是一個(gè)普通的類,所以你可以添加你自己的方法。

添加剩余的attributes到Location實(shí)體中:

longitude,類型Double

date,類型Date

locationDescription,類型String

category,類型String

placemark,類型Transformable

現(xiàn)在數(shù)據(jù)模型看起來(lái)應(yīng)該是這個(gè)樣子:

這里提一下locationDescription,你不能將這個(gè)字段命名為description,因?yàn)閐escription是NSObject中的一個(gè)對(duì)象。如果你這樣做了的話,Xcode會(huì)毫不客氣的給出一個(gè)報(bào)錯(cuò)。

placemark的類型是Transformable。Core Data僅支持比較有限的類型,比如String、Double、Date。但是placemark的類型是CLPlacemark,Core Data并不能直接支持這種類型。

幸運(yùn)的是,Core Data有一種處理任意數(shù)據(jù)的規(guī)則。任何遵循NSCoding協(xié)議的類可以被存儲(chǔ)為T(mén)ransformable類型。更加幸運(yùn)的是CLPlacemark正好遵循NSCoding協(xié)議,所以你可以直接存儲(chǔ)它,不用做什么額外的工作。

默認(rèn)情況,實(shí)體的屬性都是可選型,意味著它們可以為nil。在我們的app中只有placemark可能為nil,如果地址解析失敗的話。所以我們可以在Xcode中指明這一點(diǎn)(其實(shí)不做也無(wú)所謂)。

選擇category屬性。在屬性面板中,取消選中Optional選項(xiàng),見(jiàn)下圖:

然后用同樣方法,把除了placemark以外的所有屬性的Optional選項(xiàng)都取消選中。

然后使用command+S保存一下,雖然Xcode支持自動(dòng)保存,但是我們最好還是養(yǎng)成自己隨時(shí)保存的習(xí)慣。

數(shù)據(jù)模型基本處理完了,但是還有一件事我要特別講一下。

點(diǎn)擊選定Location實(shí)體,然后打開(kāi)屬性面板。

你可以看到Class這個(gè)字段中填寫(xiě)的是“NSManagedObject”。當(dāng)你從Core Data中取回這個(gè)實(shí)體時(shí),你會(huì)得到一個(gè)NSManagedObject類的對(duì)象。

這是Core Data管理的所有的對(duì)象的基礎(chǔ)類。常規(guī)對(duì)象從NSObject繼承,但來(lái)自Core Data的對(duì)象是由NSManagedObject擴(kuò)展來(lái)的。

因?yàn)橹苯邮褂肗SManagedObject是有點(diǎn)限制的,所以你可以使用自己的類來(lái)代替。 雖然并不是必須要這樣做,但是這樣確實(shí)會(huì)使Core Data的使用簡(jiǎn)單一些。

現(xiàn)在我們要做的是,從數(shù)據(jù)存儲(chǔ)中取回Location實(shí)體時(shí),直接返回一個(gè)Location的實(shí)例,而不是NSManagedObject。

首先在屬性面板中將Codegen這一欄中的內(nèi)容選擇為Manual/None。

??:從版本8開(kāi)始,Xcode可以從數(shù)據(jù)模型自動(dòng)生成實(shí)體類的源代碼。 Codegen設(shè)置決定了它如何做到這一點(diǎn)。 為了本教程的目的,我們暫時(shí)不使用自動(dòng)代碼生成,這就是你將Codegen設(shè)置為Manual / None的原因。 了解如何制作自己的NSManagedObject子類,而不是完全依靠Xcode是很有用的。

即使你不使用自動(dòng)類生成,Xcode仍然可以提供幫助。

選擇菜單Editor → Create NSManagedObject Subclass。

然后會(huì)彈出一個(gè)窗口讓你選擇實(shí)體和數(shù)據(jù)模型。

選擇DataModel,然后點(diǎn)擊Next。然后選擇Location再點(diǎn)擊Next。

然后選擇保存位置,點(diǎn)擊Create結(jié)束。

現(xiàn)在兩個(gè)新的文件被添加到工程中了。第一個(gè)是Location+CoreDataClass.swift,它里面的內(nèi)容是這樣的:

import Foundation
import CoreData
public class Location: NSManagedObject {
}

和你看到的一樣,Location繼承了NSManagedObject,而不是新建了一個(gè)NSObject。

第二個(gè)文件是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?

}

在這個(gè)文件中,Xcode創(chuàng)建了與實(shí)體中的屬性相對(duì)應(yīng)的實(shí)例。這里你沒(méi)有見(jiàn)過(guò)的東西是extension關(guān)鍵字。

通過(guò)extension(擴(kuò)展),你可以將其他功能添加到現(xiàn)有對(duì)象,而無(wú)需更改該對(duì)象的源代碼。 這甚至適用于你實(shí)際上沒(méi)有這些對(duì)象的源代碼。 稍后在本教程中,您將看到一個(gè)如何使用擴(kuò)展向iOS框架中的對(duì)象添加新方法的示例。

在這里,擴(kuò)展名被用于其他目的。 如果稍后更改了Core Data模型,并且想要自動(dòng)更新代碼以匹配這些更改,則可以再次選擇“創(chuàng)建NSManagedObject子類”,而Xcode將只覆蓋“Location + CoreDataProperties.swift”中的內(nèi)容,但不會(huì)添加任何內(nèi)容到Location + CoreDataClass.swift。

因此,如果你打算在之后覆蓋此文件,則更改Location + CoreDataProperties.swift并不是一個(gè)好主意。 不幸的是,Xcode在屬性的類型上有所不滿,所以你不得不對(duì)這個(gè)文件做一些修改。

首先要解決的是placemark變量。 因?yàn)槟銊?chuàng)建了一個(gè)Transformable類型的placemark,所以Xcode并不知道這是什么類型的對(duì)象,所以它選擇了泛型類型NSObject。但是你知道這將是一個(gè)CLPlacemark對(duì)象,所以你可以做些改變來(lái)讓事情變得更容易。

首先在Location+CoreDataProperties.swift中導(dǎo)入CoreLocation框架:

import CoreLocation

然后將placemark屬性修改為:

@NSManaged var placemark: CLPlacemark?

問(wèn)號(hào)不能去掉,因?yàn)閜lacemark是個(gè)可選型。

同時(shí)將date屬性的類型修改為Date:

@NSManaged var date: Date

NSDate是OC中的用法,在Swift中,我們使用Date。并且去掉問(wèn)號(hào),它不再是一個(gè)可選型。

最后,刪除category和locationDescription屬性后面的問(wèn)號(hào)。 之前你告訴Core Data這些屬性不是可選項(xiàng),所以他們不需要這個(gè)問(wèn)號(hào)。

由于這是一個(gè)托管對(duì)象,并且數(shù)據(jù)存在于數(shù)據(jù)存儲(chǔ)區(qū)內(nèi),因此Swift將以特殊方式處理Location的變量。 @NSManaged關(guān)鍵字告訴編譯器這些屬性將在運(yùn)行時(shí)由Core Data解析。 當(dāng)你為這些屬性添加一個(gè)新的值的時(shí)候,Core Data會(huì)把這個(gè)值放到數(shù)據(jù)存儲(chǔ)中以保存,而不是放在一個(gè)普通的實(shí)例變量中。

這樣就結(jié)束了這個(gè)app的數(shù)據(jù)模型的定義。 現(xiàn)在你必須把它連接到一個(gè)數(shù)據(jù)存儲(chǔ)。

數(shù)據(jù)存儲(chǔ)

在iOS系統(tǒng)中,Core Data將其所有數(shù)據(jù)存儲(chǔ)到SQLite數(shù)據(jù)庫(kù)(發(fā)音為“SQL light”)。 如果你不知道SQLite是什么,那也行。 稍后你將看到該數(shù)據(jù)庫(kù),但是你并不需要知道數(shù)據(jù)存儲(chǔ)中為了使用Core Data而做了些了什么。

但是,當(dāng)應(yīng)用程序啟動(dòng)時(shí),你必須初始化這些數(shù)據(jù)存儲(chǔ)。 對(duì)于使用Core Data的app來(lái)說(shuō),這個(gè)代碼是相同的,并且它在app的委托類中。

app委托(app delegate)是獲取與app有關(guān)的通知的對(duì)象。 例如,這是iOS通知應(yīng)用程序啟動(dòng)的地方。

你會(huì)在AppDelegate中做些修改。

打開(kāi)AppDelegate.swift并且導(dǎo)入Core Data框架(在頂部第一行):

import CoreData

在AppDelegate類的內(nèi)部添加以下代碼:

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

這是就是你需要加載之前定義的數(shù)據(jù)模型的代碼,并將其連接到SQLite數(shù)據(jù)存儲(chǔ)。

這里的目標(biāo)是創(chuàng)建一個(gè)所謂的NSManagedObjectContext對(duì)象。 這是你將用于與CoreData交談的對(duì)象。 為了獲得NSManagedObjectContext對(duì)象,應(yīng)用程序需要做幾件事情:

1、從你之前創(chuàng)建的CoreDatamodel中創(chuàng)建一個(gè)NSManagedObjectModel。 該對(duì)象表示運(yùn)行時(shí)的數(shù)據(jù)模型。 你可以從其中得到類型的實(shí)體,這些實(shí)體有什么屬性,等等。 在大多數(shù)app中,你不需要直接使用NSManagedObjectModel對(duì)象。

2、創(chuàng)建一個(gè)NSPersistentStoreCoordinator對(duì)象。該對(duì)象負(fù)責(zé)SQLite數(shù)據(jù)庫(kù)。

3、最后,創(chuàng)建NSManagedObjectContext對(duì)象并將其連接到持久存儲(chǔ)協(xié)調(diào)器。

這些對(duì)象一起被稱為“Core Data stack核心數(shù)據(jù)棧”。

在iOS 9中,你必須手動(dòng)執(zhí)行這些步驟,這可能會(huì)有點(diǎn)混亂。 幸運(yùn)的是,iOS 10中有一個(gè)新的對(duì)象NSPersistentContainer,它負(fù)責(zé)處理所有事情。

這并不意味著你應(yīng)該立即忘記你剛才了解到的NSManagedObjectModel和NSPersistentStoreCoordinator的內(nèi)容,但是它可以幫你避免編寫(xiě)一堆代碼。

剛剛添加的代碼將創(chuàng)建一個(gè)類型為NSPersistentContainer的實(shí)例變量persistentContainer。 為了得到我們之后的NSManagedObjectContext,你可以簡(jiǎn)單的訪問(wèn)一下persistentContainer的viewContext屬性。

為了方便起見(jiàn),添加一個(gè)新屬性來(lái)從持久容器中獲取NSManagedObjectContext:

 lazy var managedObjectContext: NSManagedObjectContext = self.persistentContainer.viewContext

現(xiàn)在,我們已經(jīng)做好使用CoreData的準(zhǔn)備工作了。

編譯一下app確保沒(méi)有錯(cuò)誤。 如果你運(yùn)行app,你不會(huì)發(fā)現(xiàn)任何區(qū)別,因?yàn)槟銓?shí)際上還沒(méi)有使用Core Data。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,413評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,449評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,165評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,559評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,781評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,084評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,278評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,495評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,927評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,172評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,010評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,241評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容