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閱讀 227,156評論 6 529
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,866評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,202評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,743評論 1 320
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 41,962評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,476評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,444評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,579評論 1 365
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,840評論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,177評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,568評論 2 370

推薦閱讀更多精彩內容