認識CoreData-基礎使用

建立自帶CoreData的工程正則表達式

 

在新建一個項目時,能夠勾選Use Core Data選項,這樣建立出來的工程系統會默認生成一些CoreData的代碼以及一個.xcdatamodeld後綴的模型文件,模型文件默認以工程名開頭。這些代碼在AppDelegate類中,也就是表明能夠在全局使用AppDelegate.h文件中聲明的CoreData方法和屬性。sql

 

系統默認生成的代碼是很是簡單的,只是生成了基礎的託管對象模型、託管對象上下文、持久化存儲調度器,以及MOC的save方法。可是這些代碼已經能夠完成基礎的CoreData操做了。數據庫

 

 

系統生成代碼數組

這部分代碼不該該放在AppDelegate中,尤爲對於大型項目來講,更應該把這部分代碼單獨抽離出去,放在專門的類或模塊來管理CoreData相關的邏輯。因此我通常不會經過這種方式建立CoreData,我通常都是新建一個「乾淨」的項目,而後本身往裏面添加,這樣對於CoreData的完整使用流程掌握的也比較牢固。緩存

 


 

CoreData模型文件的建立安全

 

構建模型文件多線程

 

使用CoreData的第一步是建立後綴爲.xcdatamodeld的模型文件,使用快捷鍵Command + N,選擇Core Data -> Data Model -> Next,完成模型文件的建立。併發

建立完成後能夠看到模型文件左側列表,有三個選項Entities、Fetch Requests、Configurations,分別對應着實體、請求模板、配置信息。app

 

 

添加實體框架

 

如今能夠經過長按左側列表下方的Add Entity按鈕,會彈出Add Entity、Add Fetch Request、Add Configuration選項,能夠添加實體、請求模板、配置信息。這裏先選擇Add Entity來添加一個實體,命名爲Person。

 

添加Person實體後,會發現一個實體對應着三部份內容,Attributes、Relationships、Fetched Properties,分別對應着屬性、關聯關係、獲取操做。

 

 

空實體

 

如今對Person實體添加兩個屬性,添加age屬性並設置type爲Integer 16,添加name屬性並設置type爲String。

 

 

添加屬性

 

實體屬性類型

 

在模型文件的實體中,參數類型和平時建立繼承自NSObject的模型類大致相似,可是仍是有一些關於類型的說明,下面簡單的列舉了一下。

 

  • Undefined: 默認值,參與編譯會報錯

  • Integer 16: 整數,表示範圍 -32768 ~ 32767

  • Integer 32: 整數,表示範圍 -2147483648 ~ 2147483647

  • Integer 64: 整數,表示範圍 –9223372036854775808 ~ 9223372036854775807

  • Float: 小數,經過MAXFLOAT宏定義來看,最大值用科學技術法表示是 0x1.fffffep+127f

  • Double: 小數,小數位比Float更精確,表示範圍更大

  • String: 字符串,用NSString表示

  • Boolean: 布爾值,用NSNumber表示

  • Date: 時間,用NSDate表示

  • Binary Data: 二進制,用NSData表示

  • Transformable: OC對象,用id表示。能夠在建立託管對象類文件後,手動改成對應的OC類名。使用的前提是,這個OC對象必須遵照並實現NSCoding協議

 

添加實體關聯關係

 

建立兩個實體Department和Employee,而且在這兩個實體中分別添加一些屬性,下面將會根據這兩個實體來添加關聯關係。

 

 

建立實體

 

給Employee實體添加關係,在Relationships的位置點擊加號,添加一個關聯關係。添加關係的名稱設爲department,類型設置爲Department,Inverse設置爲employee(後面會講解這個inverse的做用)。

 

 

添加Relationships

 

選擇Department實體,點擊Relationships位置的加號,添加關聯關係。

Department實體添加Relationships的操做和Employee都同樣,區別在於用紅圈標出的Type,這裏設置的To Many一對多的關係。這裏默認是To One一對一,上面的Employee就是一對一的關係。也就符合一個Department能夠有多個Employee,而Employee只能有一個Department的狀況,這也是符合常理的。

 

 

添加Relationships

 

Relationships相似於SQLite的外鍵,定義了在同一個模型中,實體與實體之間的關係。能夠定義爲對一關係或對多關係,也能夠定義單向或雙向的關係,根據需求來肯定。若是是對多的關係,默認是使用NSSet集合來存儲模型。

 

Inverse是兩個實體在Relationships中設置關聯關係後,經過設置inverse爲對應的實體,這樣能夠從一個實體找到另外一個實體,使兩個實體具備雙向的關聯關係。

 

Fetched Properties

 

在實體最下面,有一個Fetched Properties選項,這個選項用的很少,這裏就不細講了。

 

Fetched Properties用於定義查詢操做,和NSFetchRequest功能相同。定義fetchedProperty對象後,能夠經過NSManagedObjectModel類的fetchRequestFromTemplateWithName:substitutionVariables:方法或其餘相關方法獲取這個fetchedProperty對象。

 

 

獲取這個對象後,系統會默認將這個對象緩存到一個字典中,緩存以後也能夠經過fetchedProperty字典獲取fetchedProperty對象。

 

Data Model Inspector

 

選中一個實體後,右側的側邊欄(Data Model Inspector)還有不少選項,這些選項能夠對屬性進行配置。根據不一樣的屬性類型,側邊欄的顯示也不太同樣,下面是一個String類型的屬性。

 

Data Model Inspector

 

屬性設置

 

  • default Value: 設置默認值,除了二進制不能設置,其餘類型幾乎都能設置。

  • optional: 在使用時是否可選,也能夠理解爲若是設置爲NO,只要向MOC進行save操做,這個屬性是否必須有值。不然MOC進行操做時會失敗並返回一個error,該選項默認爲YES。

  • transient: 設置當前屬性是否只存在於內存,不被持久化到本地,若是設置爲YES,這個屬性就不參與持久化操做,屬性的其餘操做沒有區別。transient很是適合存儲一些在內存中緩存的數據,例如存儲臨時數據,這些數據每次都是不一樣的,並且不須要進行本地持久化,因此能夠聲明爲transient的屬性。

  • indexed: 設置當前屬性是不是索引。添加索引後能夠有效的提高檢索操做的速度。可是對於刪除這樣的操做,刪除索引後其餘地方還須要作出相應的變化,因此速度會比較慢。

  • Validation: 經過Validation能夠設置Max Value和Min Value,經過這兩個條件來約定數據,對數據的存儲進行一個驗證。數值類型都有相同的約定方式,而字符串則是約定長度,date是約定時間。

  • Reg. Ex.(Regular Expression): 能夠設置正則表達式,用來驗證和控制數據,不對數據自身產生影響。(只能應用於String類型)

  • Allows External Storage: 當存儲二進制文件時,若是遇到比較大的文件,是否存儲在存儲區以外。若是選擇YES,存儲文件大小超過1MB的文件,都會存儲在存儲區以外。不然大型文件存儲在存儲區內,會形成SQLite進行表操做時,效率受到影響。

 

Relationships設置

 

  • delete rule: 定義關聯屬性的刪除規則。在當前對象和其餘對象有關聯關係時,當前對象被刪除後與之關聯對象的反應。這個參數有四個枚舉值,代碼對應着模型文件的相同選項。

  • NSNoActionDeleteRule 刪除後沒有任何操做,也不會將關聯對象的關聯屬性指向nil。刪除後使用關聯對象的關聯屬性,可能會致使其餘問題。

  • NSNullifyDeleteRule 刪除後會將關聯對象的關聯屬性指向nil,這是默認值。

  • NSCascadeDeleteRule 刪除當前對象後,會將與之關聯的對象也一併刪除。

  • NSDenyDeleteRule 在刪除當前對象時,若是當前對象還指向其餘關聯對象,則當前對象不能被刪除。

  • Type: 主要有兩種類型,To One和To Many,表示當前關係是一對多仍是一對一。

 

實體

 

  • Parent Entity: 能夠在實體中建立繼承關係,在一個實體的菜單欄中經過Parent Entity能夠設置父實體,這樣就存在了實體的繼承關係,最後建立出來的託管模型類也是具備繼承關係的。注意繼承關係中屬性名不要相同。

 

使用了這樣的繼承關係後,系統會將子類繼承父類的數據,存在父類的表中,全部繼承自同一父類的子類都會將父類部分存放在父類的表中。這樣可能會致使父類的表中數據量過多,形成性能問題。

 

Fetch Requests

 

在模型文件中Entities下面有一個Fetch Requests,這個也是配置請求對象的。可是這個使用起來更加直觀,能夠很容易的完成一些簡單的請求配置。相對於上面講到的Fetched Properties,這個仍是更方便使用一些。

 

 

上面是對Employee實體的height屬性配置的Fetch Request,這裏配置的height要小於2米。配置以後能夠經過NSManagedObjectModel類的fetchRequestTemplateForName:方法獲取這個請求對象,參數是這個請求配置的名稱,也就是EmployeeFR。

 

Editor Style

 

這是我認爲CoreData最大的優點之一,可視化的模型文件結構。能夠很清楚的看到實體和屬性的關係,以及實體之間的對應關係。

 

 

Editor Style

 

一個.xcdatamodeld模型文件的展現風格有兩種,一種是列表的形式(Table),另外一種是圖表的形式展現(Graph)。

 

圖表看起來更加直觀,而圖表在操做上也有一些比Table更方便的地方。例如在Table的狀態下添加兩個實體的關聯關係,若是隻作一次關聯操做,默認是單向的關係。而在Graph的狀態下,按住Control對兩個圖表進行連線,兩個實體的結果就是雙向關聯的關係。

 

手動建立實體

 

假設不使用.xcdatamodeld模型文件,全都是純代碼,怎麼在項目裏建立實體啊?這樣的話就須要經過代碼建立實體描述、關聯描述等信息,而後設置給NSManagedObjectModel對象。而使用模型文件的話通常都是經過NSManagedObjectModel對象來讀取文件。

 

若是是純代碼的話,蘋果更推薦使用KVC的方式存取值,而後全部託管對象都用NSManagedObject建立。可是這樣存在的問題不少,開發成本比較大、使用不方便等等。最大的問題就是寫屬性名的key字符串,很容易出錯,並且這樣失去了CoreData原有的優勢。因此仍是推薦使用.xcdatamodeld模型文件的開發方式。

 

建立託管對象類文件

 

建立文件

 

建立實體後,就能夠根據對應的實體,生成開發中使用的基於NSManagedObject類的託管對象類文件。

仍是按照上面Department和Employee的例子,先建立一個Department實體。由於Department實體有對多關係,生成託管對象類文件的關聯屬性不同,能夠體現出和對一關係的區別,因此使用Department實體生成文件。

 

快捷鍵Command + N -> NSManagedObject subclass -> 選擇模型文件 -> 選擇實體,生成Department實體對應的託管對象類文件。

 

 

生成的託管對象類文件

 

能夠看到上面生成了四個文件,以實體名開頭的.h和.m文件,另外兩個是這個實體的Category文件。爲何生成Category文件?一會再說,先打開類文件進去看看。

 

Category

 

 

實體Category

 

能夠看到類文件中有兩個Category,分別是CoreDataProperties和CoreDataGeneratedAccessors。其中若是沒有設置對多關係的實體,只會有CoreDataProperties,而設置了對多關係的實體系統會爲其生成CoreDataGeneratedAccessors。

 

CoreDataProperties中會生成實體中聲明的Attributes和Relationships中的屬性,其中對多關係是用NSSet存儲的屬性,若是是對一的關係則是非集合的對象類型屬性。再看.m文件中,全部屬性都用@dynamic修飾,CoreData會在運行時動態爲全部Category中的屬性生成實現代碼,因此這裏用@dynamic修飾。

 

對多屬性生成的CoreDataGeneratedAccessors,是系統自動生成管理對多屬性集合的方法,通常都是一個屬性對應四個方法,方法的實現也是在運行時動態實現的,方法都是用來操做集合對象的。

 

託管對象類文件

 

點擊系統生成的託管對象類文件,此類是繼承自NSManagedObject類的。能夠看到裏面很是乾淨,沒有其餘邏輯代碼。

 

根據蘋果的註釋代碼:Insert code here to declare functionality of your managed object subclass,提示應該在這個文件中編寫此類相關的邏輯代碼。這裏就是編寫此類邏輯代碼的地方,固然也能夠什麼都不寫,看需求啦。

 

任意類型屬性

 

實體支持建立任意繼承自NSObject類的屬性,例如項目中手動建立的類。項目中建立的類在下拉列表中並不會體現,能夠在屬性類型選擇transformable類型,而後生成託管對象類文件的時候,系統會將這個屬性聲明爲id類型,在建立類文件後,能夠直接手動更改這個屬性的類型爲咱們想要的類型。

 

對於手動設置的屬性有一個要求,屬性所屬的類必須是遵照NSCoding協議,由於這個屬性要被歸檔到本地。

 

標量類型

 

建立託管對象類文件時,實體屬性的類型不管是選擇的integer32仍是float,只要是基礎數據類型,最後建立出來的默認都是NSNumber類型的,這是Xcode默認的。

 

若是須要生成的屬性類型是基礎數據類型,能夠在建立文件時勾選Use scalar properties for primitive data types選項,這樣就告訴系統須要生成標量類型屬性,建立出來的屬性就是int64_t、float這樣的基礎數據類型。

 

 

標量類型

 

更新文件

 

當前模型對應的實體發生改變後,須要從新生成模型Category文件。生成步驟和上面同樣,主要是替換Category文件,託管對象文件不會被替換。生成文件時不須要刪除,直接替換文件。

 


 

CoreData增刪改查

 

下面關於CoreData的相關操做,仍是基於上面Department和Employee的例子。而且引入了Company當作.xcdatamodeld模型文件,前面兩個實體被包含在Company中。

 

先講講NSManagedObjectContext

 

在iOS5以前建立NSManagedObjectContext對象時,都是直接經過init方法來建立。iOS5以後蘋果更加推薦使用initWithConcurrencyType:方法來建立,在建立的時候指定當前是什麼類型的併發隊列,初始化方法參數是一個枚舉值。這裏簡單說說MOC,後面多線程部分還會涉及MOC多線程相關的東西。

 

NSManagedObjectContext初始化方法的枚舉值參數主要有三個類型:

 

  • NSConfinementConcurrencyType 若是使用init方法初始化上下文,默認就是這個併發類型。在iOS9以後已經被蘋果廢棄,不建議用這個API,調用某些比較新的CoreData的API可能會致使崩潰。

  • NSPrivateQueueConcurrencyType 私有併發隊列類型,操做都是在子線程中完成的。

  • NSMainQueueConcurrencyType 主併發隊列類型,若是涉及到UI相關的操做,應該考慮使用這個參數初始化上下文。

 

若是還使用init方法,可能會對後面推出的一些API不兼容,致使多線程相關的錯誤。例以下面的錯誤,由於若是沒有顯式的設置併發類型,默認是一個已經棄用的NSConfinementConcurrencyType類型,就會致使新推出的API發生不兼容的崩潰錯誤。

 

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

 

建立MOC

 

下面是根據Company模型文件,建立了一個主隊列併發類型的MOC。

 

// 建立上下文對象,併發隊列設置爲主隊列

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

 

// 建立託管對象模型,並使用Company.momd路徑當作初始化參數

NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Company" withExtension:@"momd"];

NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];

 

// 建立持久化存儲調度器

NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

 

// 建立並關聯SQLite數據庫文件,若是已經存在則不會重複建立

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];

 

// 上下文對象設置屬性爲持久化存儲器

context.persistentStoreCoordinator = coordinator;

 

這段代碼建立了一個MOC,咱們從上往下看這段代碼。

 

momd文件

 

關於MOC的併發隊列類型上面已經簡單說了,MOC下面出現了momd的字樣,這是什麼東西?

 

 

momd文件

 

在建立後綴爲.xcdatamodeld的模型文件後,模型文件在編譯期將會被編譯爲後綴爲.momd的文件,存放在.app中,也就是Main Bundle中。在存在多個模型文件時,咱們須要經過加載不一樣的.momd文件,來建立不一樣的NSManagedObjectModel對象,每一個NSManagedObjectModel對應着不一樣的模型文件。

 

NSManagedObjectModel類中包含了模型文件中的全部entities、configurations、fetchRequests的描述。雖然.momd文件是支持存放在.app中的,其餘人能夠經過打開.app包看到這個文件。可是這個文件是通過編碼的,並不會知道這個.momd文件中的內容,因此這個文件是很是安全的。經過NSManagedObjectModel獲取模型文件描述後,來建立和關聯數據庫,並交給PSC管理。

 

若是不指定NSManagedObjectModel對應哪一個模型文件,直接使用init方法初始化NSManagedObjectModel類,系統會默認將全部模型文件的表都放在一個SQLite數據庫中。因此須要使用mainBundle中的不一樣.momd文件,對不一樣的NSManagedObjectModel進行初始化,這樣在建立數據庫時就會建立不一樣的數據庫文件。

 

持久化存儲調度器(PSC)

 

在NSManagedObjectModel下面就是NSPersistentStoreCoordinator,這個類在CoreData框架體系中起到了「中樞」的做用。對上層起到了提供簡單的調用接口,並向上層隱藏持久化實現邏輯。對下層起到了協調多個持久化存儲對象(NSPersistentStore),使下層只須要專一持久化相關邏輯。

 

 

持久化存儲調度器

 

addPersistentStoreWithType: configuration: URL: options: error:方法是PSC建立並關聯數據庫的部分,關聯本地數據庫後會返回一個NSPersistentStore類型對象,這個對象負責具體持久化存儲的實現。能夠看到這個方法是一個實例方法,也就是能夠添加多個持久化存儲對象,而且多個持久化存儲對象都關聯一個PSC,這是容許的,在上面的圖中也看到了這樣的結構。可是這樣的需求並很少,並且管理起來比較麻煩,通常都不會這樣作。

 

PSC有四種可選的持久化存儲方案,用得最多的是SQLite的方式。其中Binary和XML這兩種方式,在進行數據操做時,須要將整個文件加載到內存中,這樣對內存的消耗是很大的。

 

  • NSSQLiteStoreType : SQLite數據庫

  • NSXMLStoreType : XML文件

  • NSBinaryStoreType : 二進制文件

  • NSInMemoryStoreType : 直接存儲在內存中

 

插入操做

 

// 建立託管對象,並指明建立的託管對象所屬實體名

Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];

emp.name = @"lxz";

emp.height = @1.7;

emp.brithday = [NSDate date];

 

// 經過上下文保存對象,並在保存前判斷是否有更改

NSError *error = nil;

if (context.hasChanges) {

    [context save:&error];

}

 

// 錯誤處理

if (error) {

    NSLog(@"CoreData Insert Data Error : %@", error);

}

 

經過NSEntityDescription的insert類方法,生成並返回一個Employee託管對象,並將這個對象插入到指定的上下文中。

MOC將操做的數據存放在緩存層,只有調用MOC的save方法後,纔會真正對數據庫進行操做,不然這個對象只是存在內存中,這樣作避免了頻繁的數據庫訪問。

 

刪除操做

 

// 創建獲取數據的請求對象,指明對Employee實體進行刪除操做

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

 

// 建立謂詞對象,過濾出符合要求的對象,也就是要刪除的對象

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];

request.predicate = predicate;

 

// 執行獲取操做,找到要刪除的對象

NSError *error = nil;

NSArray *employees = [context executeFetchRequest:request error:&error];

 

// 遍歷符合刪除要求的對象數組,執行刪除操做

[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

    [context deleteObject:obj];

}];

 

// 保存上下文

if (context.hasChanges) {

    [context save:nil];

}

 

// 錯誤處理

if (error) {

    NSLog(@"CoreData Delete Data Error : %@", error);

}

 

首先獲取須要刪除的託管對象,遍歷獲取的對象數組,逐個刪除後調用MOC的save方法保存。

 

修改操做

 

// 創建獲取數據的請求對象,並指明操做的實體爲Employee

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

 

// 建立謂詞對象,設置過濾條件

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];

request.predicate = predicate;

 

// 執行獲取請求,獲取到符合要求的託管對象

NSError *error = nil;

NSArray *employees = [context executeFetchRequest:request error:&error];

[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

    obj.height = @3.f;

}];

 

// 將上面的修改進行存儲

if (context.hasChanges) {

    [context save:nil];

}

 

// 錯誤處理

if (error) {

    NSLog(@"CoreData Update Data Error : %@", error);

}

 

和上面同樣,首先獲取到須要更改的託管對象,更改完成後調用MOC的save方法持久化到本地。

 

查找操做

 

// 創建獲取數據的請求對象,指明操做的實體爲Employee

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

 

// 執行獲取操做,獲取全部Employee託管對象

NSError *error = nil;

NSArray *employees = [context executeFetchRequest:request error:&error];

[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

    NSLog(@"Employee Name : %<a href="http://www.jobbole.com/members/uz441800">@,</a> Height : %<a href="http://www.jobbole.com/members/uz441800">@,</a> Brithday : %@", obj.name, obj.height, obj.brithday);

}];

 

// 錯誤處理

if (error) {

    NSLog(@"CoreData Ergodic Data Error : %@", error);

}

 

查找操做最簡單粗暴,由於是演示代碼,因此直接將全部Employee表中的託管對象加載出來。在實際開發中確定不會這樣作,只須要加載須要的數據。後面還會講到一些更高級的操做,會涉及到獲取方面的東西。

 

總結

 

在CoreData中全部的託管對象被建立出來後,都是關聯着MOC對象的。因此在對象進行任何操做後,都會被記錄在MOC中。在最後調用MOC的save方法後,MOC會將操做交給PSC去處理,PSC將會將這個存儲任務指派給NSPersistentStore對象。

 

上面的增刪改查操做,看上去大致流程都差很少,都是一些最基礎的簡單操做,在下一篇文章中將會將一些比較複雜的操做。

相關文章
相關標籤/搜索