認(rèn)識(shí)CoreData - 基礎(chǔ)使用

該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請(qǐng)注明:劉小壯

配圖

第一篇文章中并沒(méi)有講CoreData的具體用法,只是對(duì)CoreData做了一個(gè)詳細(xì)的介紹,算是一個(gè)開(kāi)始和總結(jié)吧。

這篇文章中會(huì)主要講CoreData的基礎(chǔ)使用,以及在使用中需要注意的一些細(xì)節(jié)。因?yàn)槲恼轮袝?huì)插入代碼和圖片,內(nèi)容可能會(huì)比較多,比較考驗(yàn)各位耐心。

文章中如有疏漏或錯(cuò)誤,還請(qǐng)各位及時(shí)提出,謝謝!??`


創(chuàng)建自帶CoreData的工程

在新建一個(gè)項(xiàng)目時(shí),可以勾選Use Core Data選項(xiàng),這樣創(chuàng)建出來(lái)的工程系統(tǒng)會(huì)默認(rèn)生成一些CoreData的代碼以及一個(gè).xcdatamodeld后綴的模型文件,模型文件默認(rèn)以工程名開(kāi)頭。這些代碼在AppDelegate類中,也就是代表可以在全局使用AppDelegate.h文件中聲明的CoreData方法和屬性。

系統(tǒng)默認(rèn)生成的代碼是非常簡(jiǎn)單的,只是生成了基礎(chǔ)的托管對(duì)象模型、托管對(duì)象上下文、持久化存儲(chǔ)調(diào)度器,以及MOCsave方法。但是這些代碼已經(jīng)可以完成基礎(chǔ)的CoreData操作了。

系統(tǒng)生成代碼

這部分代碼不應(yīng)該放在AppDelegate中,尤其對(duì)于大型項(xiàng)目來(lái)說(shuō),更應(yīng)該把這部分代碼單獨(dú)抽離出去,放在專門(mén)的類或模塊來(lái)管理CoreData相關(guān)的邏輯。所以我一般不會(huì)通過(guò)這種方式創(chuàng)建CoreData,我一般都是新建一個(gè)“干凈”的項(xiàng)目,然后自己往里面添加,這樣對(duì)于CoreData的完整使用流程掌握的也比較牢固。


CoreData模型文件的創(chuàng)建

構(gòu)建模型文件

使用CoreData的第一步是創(chuàng)建后綴為.xcdatamodeld的模型文件,使用快捷鍵Command + N,選擇Core Data -> Data Model -> Next,完成模型文件的創(chuàng)建。

創(chuàng)建完成后可以看到模型文件左側(cè)列表,有三個(gè)選項(xiàng)EntitiesFetch RequestsConfigurations,分別對(duì)應(yīng)著實(shí)體、請(qǐng)求模板、配置信息。

模型文件

添加實(shí)體

現(xiàn)在可以通過(guò)長(zhǎng)按左側(cè)列表下方的Add Entity按鈕,會(huì)彈出Add EntityAdd Fetch RequestAdd Configuration選項(xiàng),可以添加實(shí)體、請(qǐng)求模板、配置信息。這里先選擇Add Entity來(lái)添加一個(gè)實(shí)體,命名為Person

添加Person實(shí)體后,會(huì)發(fā)現(xiàn)一個(gè)實(shí)體對(duì)應(yīng)著三部分內(nèi)容,AttributesRelationshipsFetched Properties,分別對(duì)應(yīng)著屬性、關(guān)聯(lián)關(guān)系、獲取操作。

空實(shí)體

現(xiàn)在對(duì)Person實(shí)體添加兩個(gè)屬性,添加age屬性并設(shè)置typeInteger 16,添加name屬性并設(shè)置typeString

添加屬性
實(shí)體屬性類型

在模型文件的實(shí)體中,參數(shù)類型和平時(shí)創(chuàng)建繼承自NSObject的模型類大體類似,但是還是有一些關(guān)于類型的說(shuō)明,下面簡(jiǎn)單的列舉了一下。

  • Undefined: 默認(rèn)值,參與編譯會(huì)報(bào)錯(cuò)
  • Integer 16: 整數(shù),表示范圍 -32768 ~ 32767
  • Integer 32: 整數(shù),表示范圍 -2147483648 ~ 2147483647
  • Integer 64: 整數(shù),表示范圍 –9223372036854775808 ~ 9223372036854775807
  • Float: 小數(shù),通過(guò)MAXFLOAT宏定義來(lái)看,最大值用科學(xué)計(jì)數(shù)法表示是 0x1.fffffep+127f
  • Double: 小數(shù),小數(shù)位比Float更精確,表示范圍更大
  • String: 字符串,用NSString表示
  • Boolean: 布爾值,用NSNumber表示
  • Date: 時(shí)間,用NSDate表示
  • Binary Data: 二進(jìn)制,用NSData表示
  • Transformable: OC對(duì)象,用id表示。可以在創(chuàng)建托管對(duì)象類文件后,手動(dòng)改為對(duì)應(yīng)的OC類名。使用的前提是,這個(gè)OC對(duì)象必須遵守并實(shí)現(xiàn)NSCoding協(xié)議

添加實(shí)體關(guān)聯(lián)關(guān)系

創(chuàng)建兩個(gè)實(shí)體DepartmentEmployee,并且在這兩個(gè)實(shí)體中分別添加一些屬性,下面將會(huì)根據(jù)這兩個(gè)實(shí)體來(lái)添加關(guān)聯(lián)關(guān)系。

創(chuàng)建實(shí)體

Employee實(shí)體添加關(guān)系,在Relationships的位置點(diǎn)擊加號(hào),添加一個(gè)關(guān)聯(lián)關(guān)系。添加關(guān)系的名稱設(shè)為department,類型設(shè)置為DepartmentInverse設(shè)置為employee(后面會(huì)講解這個(gè)inverse的作用)。

添加Relationships

選擇Department實(shí)體,點(diǎn)擊Relationships位置的加號(hào),添加關(guān)聯(lián)關(guān)系。(需要注意的是,inverse需要設(shè)置好Relationships之后才能設(shè)置)

Department實(shí)體添加Relationships的操作和Employee都一樣,區(qū)別在于用紅圈標(biāo)出的Type,這里設(shè)置的To Many一對(duì)多的關(guān)系。這里默認(rèn)是To One一對(duì)一,上面的Employee就是一對(duì)一的關(guān)系。也就符合一個(gè)Department可以有多個(gè)Employee,而Employee只能有一個(gè)Department的情況,這也是符合常理的。

添加Relationships

Relationships類似于SQLite的外鍵,定義了在同一個(gè)模型中,實(shí)體與實(shí)體之間的關(guān)系。可以定義為對(duì)一關(guān)系或?qū)Χ嚓P(guān)系,也可以定義單向或雙向的關(guān)系,根據(jù)需求來(lái)確定。如果是對(duì)多的關(guān)系,默認(rèn)是使用NSSet集合來(lái)存儲(chǔ)模型。

Inverse是兩個(gè)實(shí)體在Relationships中設(shè)置關(guān)聯(lián)關(guān)系后,通過(guò)設(shè)置inverse為對(duì)應(yīng)的實(shí)體,這樣可以從一個(gè)實(shí)體找到另一個(gè)實(shí)體,使兩個(gè)實(shí)體具有雙向的關(guān)聯(lián)關(guān)系。

Fetched Properties

在實(shí)體最下面,有一個(gè)Fetched Properties選項(xiàng),這個(gè)選項(xiàng)用的不多,這里就不細(xì)講了。

Fetched Properties用于定義查詢操作,和NSFetchRequest功能相同。定義fetchedProperty對(duì)象后,可以通過(guò)NSManagedObjectModel類的fetchRequestFromTemplateWithName:substitutionVariables:方法或其他相關(guān)方法獲取這個(gè)fetchedProperty對(duì)象。

fetched Property

獲取這個(gè)對(duì)象后,系統(tǒng)會(huì)默認(rèn)將這個(gè)對(duì)象緩存到一個(gè)字典中,緩存之后也可以通過(guò)fetchedProperty字典獲取fetchedProperty對(duì)象。

Data Model Inspector

選中一個(gè)實(shí)體后,右側(cè)的側(cè)邊欄(Data Model Inspector)還有很多選項(xiàng),這些選項(xiàng)可以對(duì)屬性進(jìn)行配置。根據(jù)不同的屬性類型,側(cè)邊欄的顯示也不太一樣,下面是一個(gè)String類型的屬性。

Data Model Inspector
屬性設(shè)置
  • default Value: 設(shè)置默認(rèn)值,除了二進(jìn)制不能設(shè)置,其他類型幾乎都能設(shè)置。

  • optional: 在使用時(shí)是否可選,也可以理解為如果設(shè)置為NO,只要向MOC進(jìn)行save操作,這個(gè)屬性是否必須有值。否則MOC進(jìn)行操作時(shí)會(huì)失敗并返回一個(gè)error,該選項(xiàng)默認(rèn)為YES

  • transient: 設(shè)置當(dāng)前屬性是否只存在于內(nèi)存,不被持久化到本地,如果設(shè)置為YES,這個(gè)屬性就不參與持久化操作,屬性的其他操作沒(méi)有區(qū)別。transient非常適合存儲(chǔ)一些在內(nèi)存中緩存的數(shù)據(jù),例如存儲(chǔ)臨時(shí)數(shù)據(jù),這些數(shù)據(jù)每次都是不同的,而且不需要進(jìn)行本地持久化,所以可以聲明為transient的屬性。

  • indexed: 設(shè)置當(dāng)前屬性是否是索引。添加索引后可以有效的提升檢索操作的速度。但是對(duì)于刪除這樣的操作,刪除索引后其他地方還需要做出相應(yīng)的變化,所以速度會(huì)比較慢。

  • Validation: 通過(guò)Validation可以設(shè)置Max ValueMin Value,通過(guò)這兩個(gè)條件來(lái)約定數(shù)據(jù),對(duì)數(shù)據(jù)的存儲(chǔ)進(jìn)行一個(gè)驗(yàn)證。數(shù)值類型都有相同的約定方式,而字符串則是約定長(zhǎng)度,date是約定時(shí)間。

  • Reg. Ex.(Regular Expression): 可以設(shè)置正則表達(dá)式,用來(lái)驗(yàn)證和控制數(shù)據(jù),不對(duì)數(shù)據(jù)自身產(chǎn)生影響。(只能應(yīng)用于String類型)

  • Allows External Storage: 當(dāng)存儲(chǔ)二進(jìn)制文件時(shí),如果遇到比較大的文件,是否存儲(chǔ)在存儲(chǔ)區(qū)之外。如果選擇YES,存儲(chǔ)文件大小超過(guò)1MB的文件,都會(huì)存儲(chǔ)在存儲(chǔ)區(qū)之外。否則大型文件存儲(chǔ)在存儲(chǔ)區(qū)內(nèi),會(huì)造成SQLite進(jìn)行表操作時(shí),效率受到影響。

Relationships設(shè)置
  • delete rule: 定義關(guān)聯(lián)屬性的刪除規(guī)則。在當(dāng)前對(duì)象和其他對(duì)象有關(guān)聯(lián)關(guān)系時(shí),當(dāng)前對(duì)象被刪除后與之關(guān)聯(lián)對(duì)象的反應(yīng)。這個(gè)參數(shù)有四個(gè)枚舉值,代碼對(duì)應(yīng)著模型文件的相同選項(xiàng)。

NSNoActionDeleteRule 刪除后沒(méi)有任何操作,也不會(huì)將關(guān)聯(lián)對(duì)象的關(guān)聯(lián)屬性指向nil。刪除后使用關(guān)聯(lián)對(duì)象的關(guān)聯(lián)屬性,可能會(huì)導(dǎo)致其他問(wèn)題。
NSNullifyDeleteRule 刪除后會(huì)將關(guān)聯(lián)對(duì)象的關(guān)聯(lián)屬性指向nil,這是默認(rèn)值。
NSCascadeDeleteRule 刪除當(dāng)前對(duì)象后,會(huì)將與之關(guān)聯(lián)的對(duì)象也一并刪除。
NSDenyDeleteRule 在刪除當(dāng)前對(duì)象時(shí),如果當(dāng)前對(duì)象還指向其他關(guān)聯(lián)對(duì)象,則當(dāng)前對(duì)象不能被刪除。

  • Type: 主要有兩種類型,To OneTo Many,表示當(dāng)前關(guān)系是一對(duì)多還是一對(duì)一。
實(shí)體
  • Parent Entity: 可以在實(shí)體中創(chuàng)建繼承關(guān)系,在一個(gè)實(shí)體的菜單欄中通過(guò)Parent Entity可以設(shè)置父實(shí)體,這樣就存在了實(shí)體的繼承關(guān)系,最后創(chuàng)建出來(lái)的托管模型類也是具有繼承關(guān)系的。注意繼承關(guān)系中屬性名不要相同。
    使用了這樣的繼承關(guān)系后,系統(tǒng)會(huì)將子類繼承父類的數(shù)據(jù),存在父類的表中,所有繼承自同一父類的子類都會(huì)將父類部分存放在父類的表中。這樣可能會(huì)導(dǎo)致父類的表中數(shù)據(jù)量過(guò)多,造成性能問(wèn)題。

Fetch Requests

在模型文件中Entities下面有一個(gè)Fetch Requests,這個(gè)也是配置請(qǐng)求對(duì)象的。但是這個(gè)使用起來(lái)更加直觀,可以很容易的完成一些簡(jiǎn)單的請(qǐng)求配置。相對(duì)于上面講到的Fetched Properties,這個(gè)還是更方便使用一些。

Fetch Requests

上面是對(duì)Employee實(shí)體的height屬性配置的Fetch Request,這里配置的height小于2米。配置之后可以通過(guò)NSManagedObjectModel類的fetchRequestTemplateForName:方法獲取這個(gè)請(qǐng)求對(duì)象,參數(shù)是這個(gè)請(qǐng)求配置的名稱,也就是EmployeeFR

Editor Style

這是我認(rèn)為CoreData最大的優(yōu)勢(shì)之一,可視化的模型文件結(jié)構(gòu)。可以很清楚的看到實(shí)體和屬性的關(guān)系,以及實(shí)體之間的對(duì)應(yīng)關(guān)系。

Editor Style

一個(gè).xcdatamodeld模型文件的展示風(fēng)格有兩種,一種是列表的形式(Table),另一種是圖表的形式展示(Graph)。

圖表看起來(lái)更加直觀,而圖表在操作上也有一些比Table更方便的地方。例如在Table的狀態(tài)下添加兩個(gè)實(shí)體的關(guān)聯(lián)關(guān)系,如果只做一次關(guān)聯(lián)操作,默認(rèn)是單向的關(guān)系。而在Graph的狀態(tài)下,按住Control對(duì)兩個(gè)圖表進(jìn)行連線,兩個(gè)實(shí)體的結(jié)果就是雙向關(guān)聯(lián)的關(guān)系。

手動(dòng)創(chuàng)建實(shí)體

假設(shè)不使用.xcdatamodeld模型文件,全都是純代碼,怎么在項(xiàng)目里創(chuàng)建實(shí)體啊?這樣的話就需要通過(guò)代碼創(chuàng)建實(shí)體描述、關(guān)聯(lián)描述等信息,然后設(shè)置給NSManagedObjectModel對(duì)象。而使用模型文件的話一般都是通過(guò)NSManagedObjectModel對(duì)象來(lái)讀取文件。

如果是純代碼的話,蘋(píng)果更推薦使用KVC的方式存取值,然后所有托管對(duì)象都用NSManagedObject創(chuàng)建。但是這樣存在的問(wèn)題很多,開(kāi)發(fā)成本比較大、使用不方便等等。最大的問(wèn)題就是寫(xiě)屬性名的key字符串,很容易出錯(cuò),而且這樣失去了CoreData原有的優(yōu)點(diǎn)。所以還是推薦使用.xcdatamodeld模型文件的開(kāi)發(fā)方式。

創(chuàng)建托管對(duì)象類文件

創(chuàng)建文件

創(chuàng)建實(shí)體后,就可以根據(jù)對(duì)應(yīng)的實(shí)體,生成開(kāi)發(fā)中使用的基于NSManagedObject類的托管對(duì)象類文件。

還是按照上面DepartmentEmployee的例子,先創(chuàng)建一個(gè)Department實(shí)體。因?yàn)?code>Department實(shí)體有對(duì)多關(guān)系,生成托管對(duì)象類文件的關(guān)聯(lián)屬性不一樣,可以體現(xiàn)出和對(duì)一關(guān)系的區(qū)別,所以使用Department實(shí)體生成文件。

點(diǎn)擊后綴名為.xcdatamodeld的模型文件,選擇XcodeEditor -> Create NSManagedObject Subclass -> 選擇模型文件 -> 選擇實(shí)體,生成Department實(shí)體對(duì)應(yīng)的托管對(duì)象類文件。

生成的托管對(duì)象類文件

可以看到上面生成了四個(gè)文件,以實(shí)體名開(kāi)頭的.h.m文件,另外兩個(gè)是這個(gè)實(shí)體的Category文件。為什么生成Category文件?一會(huì)再說(shuō),先打開(kāi)類文件進(jìn)去看看。

Category

實(shí)體Category

可以看到類文件中有兩個(gè)Category,分別是CoreDataPropertiesCoreDataGeneratedAccessors。其中如果沒(méi)有設(shè)置對(duì)多關(guān)系的實(shí)體,只會(huì)有CoreDataProperties,而設(shè)置了對(duì)多關(guān)系的實(shí)體系統(tǒng)會(huì)為其生成CoreDataGeneratedAccessors

CoreDataProperties中會(huì)生成實(shí)體中聲明的AttributesRelationships中的屬性,其中對(duì)多關(guān)系是用NSSet存儲(chǔ)的屬性,如果是對(duì)一的關(guān)系則是非集合的對(duì)象類型屬性。再看.m文件中,所有屬性都用@dynamic修飾,CoreData會(huì)在運(yùn)行時(shí)動(dòng)態(tài)為所有Category中的屬性生成實(shí)現(xiàn)代碼,所以這里用@dynamic修飾。

對(duì)多屬性生成的CoreDataGeneratedAccessors,是系統(tǒng)自動(dòng)生成管理對(duì)多屬性集合的方法,一般都是一個(gè)屬性對(duì)應(yīng)四個(gè)方法,方法的實(shí)現(xiàn)也是在運(yùn)行時(shí)動(dòng)態(tài)實(shí)現(xiàn)的,方法都是用來(lái)操作集合對(duì)象的。

托管對(duì)象類文件

點(diǎn)擊系統(tǒng)生成的托管對(duì)象類文件,此類是繼承自NSManagedObject類的。可以看到里面非常干凈,沒(méi)有其他邏輯代碼。

根據(jù)蘋(píng)果的注釋代碼:Insert code here to declare functionality of your managed object subclass,提示應(yīng)該在這個(gè)文件中編寫(xiě)此類相關(guān)的邏輯代碼。這里就是編寫(xiě)此類邏輯代碼的地方,當(dāng)然也可以什么都不寫(xiě),看需求啦。

任意類型屬性

實(shí)體支持創(chuàng)建任意繼承自NSObject類的屬性,例如項(xiàng)目中手動(dòng)創(chuàng)建的類。項(xiàng)目中創(chuàng)建的類在下拉列表中并不會(huì)體現(xiàn),可以在屬性類型選擇transformable類型,然后生成托管對(duì)象類文件的時(shí)候,系統(tǒng)會(huì)將這個(gè)屬性聲明為id類型,在創(chuàng)建類文件后,可以直接手動(dòng)更改這個(gè)屬性的類型為我們想要的類型。

對(duì)于手動(dòng)設(shè)置的屬性有一個(gè)要求,屬性所屬的類必須是遵守NSCoding協(xié)議,因?yàn)檫@個(gè)屬性要被歸檔到本地。

標(biāo)量類型

創(chuàng)建托管對(duì)象類文件時(shí),實(shí)體屬性的類型無(wú)論是選擇的integer32還是float,只要是基礎(chǔ)數(shù)據(jù)類型,最后創(chuàng)建出來(lái)的默認(rèn)都是NSNumber類型的,這是Xcode默認(rèn)的。

如果需要生成的屬性類型是基礎(chǔ)數(shù)據(jù)類型,可以在創(chuàng)建文件時(shí)勾選Use scalar properties for primitive data types選項(xiàng),這樣就告訴系統(tǒng)需要生成標(biāo)量類型屬性,創(chuàng)建出來(lái)的屬性就是int64_tfloat這樣的基礎(chǔ)數(shù)據(jù)類型。

標(biāo)量類型

更新文件

當(dāng)前模型對(duì)應(yīng)的實(shí)體發(fā)生改變后,需要重新生成模型Category文件。生成步驟和上面一樣,主要是替換Category文件,托管對(duì)象文件不會(huì)被替換。生成文件時(shí)不需要?jiǎng)h除,直接替換文件


CoreData增刪改查

下面關(guān)于CoreData的相關(guān)操作,還是基于上面DepartmentEmployee的例子。并且引入了Company當(dāng)做.xcdatamodeld模型文件,前面兩個(gè)實(shí)體被包含在Company中。

先講講NSManagedObjectContext

iOS5之前創(chuàng)建NSManagedObjectContext對(duì)象時(shí),都是直接通過(guò)init方法來(lái)創(chuàng)建。iOS5之后蘋(píng)果更加推薦使用initWithConcurrencyType:方法來(lái)創(chuàng)建,在創(chuàng)建的時(shí)候指定當(dāng)前是什么類型的并發(fā)隊(duì)列,初始化方法參數(shù)是一個(gè)枚舉值。這里簡(jiǎn)單說(shuō)說(shuō)MOC,后面多線程部分還會(huì)涉及MOC多線程相關(guān)的東西。

NSManagedObjectContext初始化方法的枚舉值參數(shù)主要有三個(gè)類型:

  • NSConfinementConcurrencyType 如果使用init方法初始化上下文,默認(rèn)就是這個(gè)并發(fā)類型。在iOS9之后已經(jīng)被蘋(píng)果廢棄,不建議用這個(gè)API,調(diào)用某些比較新的CoreDataAPI可能會(huì)導(dǎo)致崩潰。

  • NSPrivateQueueConcurrencyType 私有并發(fā)隊(duì)列類型,操作都是在子線程中完成的。

  • NSMainQueueConcurrencyType 主并發(fā)隊(duì)列類型,如果涉及到UI相關(guān)的操作,應(yīng)該考慮使用這個(gè)參數(shù)初始化上下文。

如果還使用init方法,可能會(huì)對(duì)后面推出的一些API不兼容,導(dǎo)致多線程相關(guān)的錯(cuò)誤。例如下面的錯(cuò)誤,因?yàn)槿绻麤](méi)有顯式的設(shè)置并發(fā)類型,默認(rèn)是一個(gè)已經(jīng)棄用的NSConfinementConcurrencyType類型,就會(huì)導(dǎo)致新推出的API發(fā)生不兼容的崩潰錯(cuò)誤。

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConfinementConcurrencyType context

創(chuàng)建MOC

下面是根據(jù)Company模型文件,創(chuàng)建了一個(gè)主隊(duì)列并發(fā)類型的MOC

// 創(chuàng)建上下文對(duì)象,并發(fā)隊(duì)列設(shè)置為主隊(duì)列
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// 創(chuàng)建托管對(duì)象模型,并使用Company.momd路徑當(dāng)做初始化參數(shù)
NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Company" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];

// 創(chuàng)建持久化存儲(chǔ)調(diào)度器
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

// 創(chuàng)建并關(guān)聯(lián)SQLite數(shù)據(jù)庫(kù)文件,如果已經(jīng)存在則不會(huì)重復(fù)創(chuàng)建
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"Company"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];

// 上下文對(duì)象設(shè)置屬性為持久化存儲(chǔ)器
context.persistentStoreCoordinator = coordinator;

這段代碼創(chuàng)建了一個(gè)MOC,我們從上往下看這段代碼。

momd文件

關(guān)于MOC的并發(fā)隊(duì)列類型上面已經(jīng)簡(jiǎn)單說(shuō)了,MOC下面出現(xiàn)了momd的字樣,這是什么東西?

momd文件

在創(chuàng)建后綴為.xcdatamodeld的模型文件后,模型文件在編譯期將會(huì)被編譯為后綴為.momd的文件,存放在.app中,也就是Main Bundle中。在存在多個(gè)模型文件時(shí),我們需要通過(guò)加載不同的.momd文件,來(lái)創(chuàng)建不同的NSManagedObjectModel對(duì)象,每個(gè)NSManagedObjectModel對(duì)應(yīng)著不同的模型文件。

NSManagedObjectModel類中包含了模型文件中的所有entitiesconfigurationsfetchRequests的描述。雖然.momd文件是支持存放在.app中的,其他人可以通過(guò)打開(kāi).app包看到這個(gè)文件。但是這個(gè)文件是經(jīng)過(guò)編碼的,并不會(huì)知道這個(gè).momd文件中的內(nèi)容,所以這個(gè)文件是非常安全的。通過(guò)NSManagedObjectModel獲取模型文件描述后,來(lái)創(chuàng)建和關(guān)聯(lián)數(shù)據(jù)庫(kù),并交給PSC管理。

如果不指定NSManagedObjectModel對(duì)應(yīng)哪個(gè)模型文件,直接使用init方法初始化NSManagedObjectModel類,系統(tǒng)會(huì)默認(rèn)將所有模型文件的表都放在一個(gè)SQLite數(shù)據(jù)庫(kù)中。所以需要使用mainBundle中的不同.momd文件,對(duì)不同的NSManagedObjectModel進(jìn)行初始化,這樣在創(chuàng)建數(shù)據(jù)庫(kù)時(shí)就會(huì)創(chuàng)建不同的數(shù)據(jù)庫(kù)文件。

持久化存儲(chǔ)調(diào)度器(PSC)

NSManagedObjectModel下面就是NSPersistentStoreCoordinator,這個(gè)類在CoreData框架體系中起到了“中樞”的作用。對(duì)上層起到了提供簡(jiǎn)單的調(diào)用接口,并向上層隱藏持久化實(shí)現(xiàn)邏輯。對(duì)下層起到了協(xié)調(diào)多個(gè)持久化存儲(chǔ)對(duì)象(NSPersistentStore),使下層只需要專注持久化相關(guān)邏輯。

持久化存儲(chǔ)調(diào)度器

addPersistentStoreWithType: configuration: URL: options: error:方法是PSC創(chuàng)建并關(guān)聯(lián)數(shù)據(jù)庫(kù)的部分,關(guān)聯(lián)本地?cái)?shù)據(jù)庫(kù)后會(huì)返回一個(gè)NSPersistentStore類型對(duì)象,這個(gè)對(duì)象負(fù)責(zé)具體持久化存儲(chǔ)的實(shí)現(xiàn)。可以看到這個(gè)方法是一個(gè)實(shí)例方法,也就是可以添加多個(gè)持久化存儲(chǔ)對(duì)象,并且多個(gè)持久化存儲(chǔ)對(duì)象都關(guān)聯(lián)一個(gè)PSC,這是允許的,在上面的圖中也看到了這樣的結(jié)構(gòu)。但是這樣的需求并不多,而且管理起來(lái)比較麻煩,一般都不會(huì)這樣做。

PSC有四種可選的持久化存儲(chǔ)方案,用得最多的是SQLite的方式。其中BinaryXML這兩種方式,在進(jìn)行數(shù)據(jù)操作時(shí),需要將整個(gè)文件加載到內(nèi)存中,這樣對(duì)內(nèi)存的消耗是很大的。

  • NSSQLiteStoreType : SQLite數(shù)據(jù)庫(kù)
  • NSXMLStoreType : XML文件
  • NSBinaryStoreType : 二進(jìn)制文件
  • NSInMemoryStoreType : 直接存儲(chǔ)在內(nèi)存中

插入操作

// 創(chuàng)建托管對(duì)象,并指明創(chuàng)建的托管對(duì)象所屬實(shí)體名
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
emp.name = @"lxz";
emp.height = @1.7;
emp.brithday = [NSDate date];

// 通過(guò)上下文保存對(duì)象,并在保存前判斷是否有更改
NSError *error = nil;
if (context.hasChanges) {
    [context save:&error];
}

// 錯(cuò)誤處理
if (error) {
    NSLog(@"CoreData Insert Data Error : %@", error);
}   

通過(guò)NSEntityDescriptioninsert類方法,生成并返回一個(gè)Employee托管對(duì)象,并將這個(gè)對(duì)象插入到指定的上下文中。

MOC將操作的數(shù)據(jù)存放在緩存層,只有調(diào)用MOCsave方法后,才會(huì)真正對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,否則這個(gè)對(duì)象只是存在內(nèi)存中,這樣做避免了頻繁的數(shù)據(jù)庫(kù)訪問(wèn)。

刪除操作

// 建立獲取數(shù)據(jù)的請(qǐng)求對(duì)象,指明對(duì)Employee實(shí)體進(jìn)行刪除操作
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 創(chuàng)建謂詞對(duì)象,過(guò)濾出符合要求的對(duì)象,也就是要?jiǎng)h除的對(duì)象
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 執(zhí)行獲取操作,找到要?jiǎng)h除的對(duì)象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];

// 遍歷符合刪除要求的對(duì)象數(shù)組,執(zhí)行刪除操作
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    [context deleteObject:obj];
}];

// 保存上下文
if (context.hasChanges) {
    [context save:nil];
}

// 錯(cuò)誤處理
if (error) {
    NSLog(@"CoreData Delete Data Error : %@", error);
}

首先獲取需要?jiǎng)h除的托管對(duì)象,遍歷獲取的對(duì)象數(shù)組,逐個(gè)刪除后調(diào)用MOCsave方法保存。

修改操作

// 建立獲取數(shù)據(jù)的請(qǐng)求對(duì)象,并指明操作的實(shí)體為Employee
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 創(chuàng)建謂詞對(duì)象,設(shè)置過(guò)濾條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 執(zhí)行獲取請(qǐng)求,獲取到符合要求的托管對(duì)象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    obj.height = @3.f;
}];

// 將上面的修改進(jìn)行存儲(chǔ)
if (context.hasChanges) {
    [context save:nil];
}

// 錯(cuò)誤處理
if (error) {
    NSLog(@"CoreData Update Data Error : %@", error);
}

和上面一樣,首先獲取到需要更改的托管對(duì)象,更改完成后調(diào)用MOCsave方法持久化到本地。

查找操作

// 建立獲取數(shù)據(jù)的請(qǐng)求對(duì)象,指明操作的實(shí)體為Employee
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 執(zhí)行獲取操作,獲取所有Employee托管對(duì)象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Employee Name : %@, Height : %@, Brithday : %@", obj.name, obj.height, obj.brithday);
}];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"CoreData Ergodic Data Error : %@", error);
}

查找操作最簡(jiǎn)單粗暴,因?yàn)槭茄菔敬a,所以直接將所有Employee表中的托管對(duì)象加載出來(lái)。在實(shí)際開(kāi)發(fā)中肯定不會(huì)這樣做,只需要加載需要的數(shù)據(jù)。后面還會(huì)講到一些更高級(jí)的操作,會(huì)涉及到獲取方面的東西。

總結(jié)

CoreData中所有的托管對(duì)象被創(chuàng)建出來(lái)后,都是關(guān)聯(lián)著MOC對(duì)象的。所以在對(duì)象進(jìn)行任何操作后,都會(huì)被記錄在MOC中。在最后調(diào)用MOCsave方法后,MOC會(huì)將操作交給PSC去處理,PSC將會(huì)將這個(gè)存儲(chǔ)任務(wù)指派給NSPersistentStore對(duì)象。

上面的增刪改查操作,看上去大體流程都差不多,都是一些最基礎(chǔ)的簡(jiǎn)單操作,在下一篇文章中將會(huì)將一些比較復(fù)雜的操作。


好多同學(xué)都問(wèn)我有Demo沒(méi)有,其實(shí)文章中貼出的代碼組合起來(lái)就是個(gè)Demo。后來(lái)想了想,還是給本系列文章配了一個(gè)簡(jiǎn)單的Demo,方便大家運(yùn)行調(diào)試,后續(xù)會(huì)給所有博客的文章都加上Demo

Demo只是來(lái)輔助讀者更好的理解文章中的內(nèi)容,應(yīng)該博客結(jié)合Demo一起學(xué)習(xí),只看Demo還是不能理解更深層的原理Demo中幾乎每一行代碼都會(huì)有注釋,各位可以打斷點(diǎn)跟著Demo執(zhí)行流程走一遍,看看各個(gè)階段變量的值。

Demo地址劉小壯的Github


這兩天更新了一下文章,將CoreData系列的六篇文章整合在一起,做了一個(gè)PDF版的《CoreData Book》,放在我Github上了。PDF上有文章目錄,方便閱讀。

如果你覺(jué)得不錯(cuò),請(qǐng)把PDF幫忙轉(zhuǎn)到其他群里,或者你的朋友,讓更多的人了解CoreData,衷心感謝!??

最后編輯于
?著作權(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閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

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