<簡書 — 劉小壯> http://www.jianshu.com/p/0ddfa35c7898git
第一篇文章中並無講
CoreData
的具體用法,只是對CoreData
作了一個詳細的介紹,算是一個開始和總結吧。github這篇文章中會主要講
CoreData
的基礎使用,以及在使用中須要注意的一些細節。由於文章中會插入代碼和圖片,內容可能會比較多,比較考驗各位耐心。正則表達式文章中若有疏漏或錯誤,還請各位及時提出,謝謝!`sql
在新建一個項目時,能夠勾選Use Core Data
選項,這樣建立出來的工程系統會默認生成一些CoreData
的代碼以及一個.xcdatamodeld
後綴的模型文件,模型文件默認以工程名開頭。這些代碼在AppDelegate
類中,也就是表明能夠在全局使用AppDelegate.h
文件中聲明的CoreData
方法和屬性。數據庫
系統默認生成的代碼是很是簡單的,只是生成了基礎的託管對象模型、託管對象上下文、持久化存儲調度器,以及MOC
的save
方法。可是這些代碼已經能夠完成基礎的CoreData
操做了。數組
這部分代碼不該該放在AppDelegate
中,尤爲對於大型項目來講,更應該把這部分代碼單獨抽離出去,放在專門的類或模塊來管理CoreData
相關的邏輯。因此我通常不會經過這種方式建立CoreData
,我通常都是新建一個**「乾淨」**的項目,而後本身往裏面添加,這樣對於CoreData
的完整使用流程掌握的也比較牢固。緩存
使用CoreData
的第一步是建立後綴爲.xcdatamodeld
的模型文件,使用快捷鍵Command + N,選擇Core Data -> Data Model -> Next
,完成模型文件的建立。安全
建立完成後能夠看到模型文件左側列表,有三個選項Entities
、Fetch Requests
、Configurations
,分別對應着實體、請求模板、配置信息。多線程
如今能夠經過長按左側列表下方的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
的模型類大致相似,可是仍是有一些關於類型的說明,下面簡單的列舉了一下。
-32768 ~ 32767
-2147483648 ~ 2147483647
–9223372036854775808 ~ 9223372036854775807
MAXFLOAT
宏定義來看,最大值用科學計數法表示是 0x1.fffffep+127f
Float
更精確,表示範圍更大NSString
表示NSNumber
表示NSDate
表示NSData
表示OC
對象,用id
表示。能夠在建立託管對象類文件後,手動改成對應的OC
類名。使用的前提是,這個OC
對象必須遵照並實現NSCoding
協議建立兩個實體Department
和Employee
,而且在這兩個實體中分別添加一些屬性,下面將會根據這兩個實體來添加關聯關係。
給Employee
實體添加關係,在Relationships
的位置點擊加號,添加一個關聯關係。添加關係的名稱設爲department
,類型設置爲Department
,Inverse
設置爲employee
(後面會講解這個inverse
的做用)。
選擇Department
實體,點擊Relationships
位置的加號,添加關聯關係。(須要注意的是,inverse
須要設置好Relationships
以後才能設置)
Department
實體添加Relationships
的操做和Employee
都同樣,區別在於用紅圈標出的Type
,這裏設置的To Many
一對多的關係。這裏默認是To One
一對一,上面的Employee
就是一對一的關係。也就符合一個Department
能夠有多個Employee
,而Employee
只能有一個Department
的狀況,這也是符合常理的。
Relationships
相似於SQLite
的外鍵,定義了在同一個模型中,實體與實體之間的關係。能夠定義爲對一關係或對多關係,也能夠定義單向或雙向的關係,根據需求來肯定。若是是對多的關係,默認是使用NSSet
集合來存儲模型。
Inverse
是兩個實體在Relationships
中設置關聯關係後,經過設置inverse
爲對應的實體,這樣能夠從一個實體找到另外一個實體,使兩個實體具備雙向的關聯關係。
在實體最下面,有一個Fetched Properties
選項,這個選項用的很少,這裏就不細講了。
Fetched Properties
用於定義查詢操做,和NSFetchRequest
功能相同。定義fetchedProperty
對象後,能夠經過NSManagedObjectModel
類的fetchRequestFromTemplateWithName:substitutionVariables:
方法或其餘相關方法獲取這個fetchedProperty
對象。
獲取這個對象後,系統會默認將這個對象緩存到一個字典中,緩存以後也能夠經過fetchedProperty
字典獲取fetchedProperty
對象。
選中一個實體後,右側的側邊欄(Data Model Inspector
)還有不少選項,這些選項能夠對屬性進行配置。根據不一樣的屬性類型,側邊欄的顯示也不太同樣,下面是一個String
類型的屬性。
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
進行表操做時,效率受到影響。
NSNoActionDeleteRule 刪除後沒有任何操做,也不會將關聯對象的關聯屬性指向nil
。刪除後使用關聯對象的關聯屬性,可能會致使其餘問題。 NSNullifyDeleteRule 刪除後會將關聯對象的關聯屬性指向nil
,這是默認值。 NSCascadeDeleteRule 刪除當前對象後,會將與之關聯的對象也一併刪除。 NSDenyDeleteRule 在刪除當前對象時,若是當前對象還指向其餘關聯對象,則當前對象不能被刪除。
To One
和To Many
,表示當前關係是一對多仍是一對一。Parent Entity
能夠設置父實體,這樣就存在了實體的繼承關係,最後建立出來的託管模型類也是具備繼承關係的。注意繼承關係中屬性名不要相同。 使用了這樣的繼承關係後,系統會將子類繼承父類的數據,存在父類的表中,全部繼承自同一父類的子類都會將父類部分存放在父類的表中。這樣可能會致使父類的表中數據量過多,形成性能問題。在模型文件中Entities
下面有一個Fetch Requests
,這個也是配置請求對象的。可是這個使用起來更加直觀,能夠很容易的完成一些簡單的請求配置。相對於上面講到的Fetched Properties
,這個仍是更方便使用一些。
上面是對Employee
實體的height
屬性配置的Fetch Request
,這裏配置的height
要小於2米。配置以後能夠經過NSManagedObjectModel
類的fetchRequestTemplateForName:
方法獲取這個請求對象,參數是這個請求配置的名稱,也就是EmployeeFR
。
這是我認爲CoreData
最大的優點之一,可視化的模型文件結構。能夠很清楚的看到實體和屬性的關係,以及實體之間的對應關係。
一個.xcdatamodeld
模型文件的展現風格有兩種,一種是列表的形式(Table
),另外一種是圖表的形式展現(Graph
)。
圖表看起來更加直觀,而圖表在操做上也有一些比Table
更方便的地方。例如在Table
的狀態下添加兩個實體的關聯關係,若是隻作一次關聯操做,默認是單向的關係。而在Graph
的狀態下,按住Control
對兩個圖表進行連線,兩個實體的結果就是雙向關聯的關係。
假設不使用.xcdatamodeld
模型文件,全都是純代碼,怎麼在項目裏建立實體啊?這樣的話就須要經過代碼建立實體描述、關聯描述等信息,而後設置給NSManagedObjectModel
對象。而使用模型文件的話通常都是經過NSManagedObjectModel
對象來讀取文件。
若是是純代碼的話,蘋果更推薦使用KVC
的方式存取值,而後全部託管對象都用NSManagedObject
建立。可是這樣存在的問題不少,開發成本比較大、使用不方便等等。最大的問題就是寫屬性名的key
字符串,很容易出錯,並且這樣失去了CoreData
原有的優勢。因此仍是推薦使用.xcdatamodeld
模型文件的開發方式。
建立實體後,就能夠根據對應的實體,生成開發中使用的基於NSManagedObject
類的託管對象類文件。
仍是按照上面Department
和Employee
的例子,先建立一個Department
實體。由於Department
實體有對多關係,生成託管對象類文件的關聯屬性不同,能夠體現出和對一關係的區別,因此使用Department
實體生成文件。
點擊後綴名爲.xcdatamodeld
的模型文件,選擇Xcode
的Editor -> Create NSManagedObject Subclass -> 選擇模型文件 -> 選擇實體
,生成Department
實體對應的託管對象類文件。
能夠看到上面生成了四個文件,以實體名開頭的.h
和.m
文件,另外兩個是這個實體的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
的相關操做,仍是基於上面Department
和Employee
的例子。而且引入了Company
當作.xcdatamodeld
模型文件,前面兩個實體被包含在Company
中。
在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
下面是根據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
,咱們從上往下看這段代碼。
關於MOC
的併發隊列類型上面已經簡單說了,MOC
下面出現了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
進行初始化,這樣在建立數據庫時就會建立不一樣的數據庫文件。
在NSManagedObjectModel
下面就是NSPersistentStoreCoordinator
,這個類在CoreData
框架體系中起到了**「中樞」的做用。對上層起到了提供簡單的調用接口,並向上層隱藏持久化實現邏輯。對下層起到了協調多個持久化存儲對象**(NSPersistentStore
),使下層只須要專一持久化相關邏輯。
addPersistentStoreWithType: configuration: URL: options: error:
方法是PSC
建立並關聯數據庫的部分,關聯本地數據庫後會返回一個NSPersistentStore
類型對象,這個對象負責具體持久化存儲的實現。能夠看到這個方法是一個實例方法,也就是能夠添加多個持久化存儲對象,而且多個持久化存儲對象都關聯一個PSC
,這是容許的,在上面的圖中也看到了這樣的結構。可是這樣的需求並很少,並且管理起來比較麻煩,通常都不會這樣作。
PSC
有四種可選的持久化存儲方案,用得最多的是SQLite
的方式。其中Binary
和XML
這兩種方式,在進行數據操做時,須要將整個文件加載到內存中,這樣對內存的消耗是很大的。
// 建立託管對象,並指明建立的託管對象所屬實體名 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<Employee *> *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<Employee *> *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<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); }]; // 錯誤處理 if (error) { NSLog(@"CoreData Ergodic Data Error : %@", error); }
查找操做最簡單粗暴,由於是演示代碼,因此直接將全部Employee
表中的託管對象加載出來。在實際開發中確定不會這樣作,只須要加載須要的數據。後面還會講到一些更高級的操做,會涉及到獲取方面的東西。
在CoreData
中全部的託管對象被建立出來後,都是關聯着MOC
對象的。因此在對象進行任何操做後,都會被記錄在MOC
中。在最後調用MOC
的save
方法後,MOC
會將操做交給PSC
去處理,PSC
將會將這個存儲任務指派給NSPersistentStore
對象。
上面的增刪改查操做,看上去大致流程都差很少,都是一些最基礎的簡單操做,在下一篇文章中將會將一些比較複雜的操做。
好多同窗都問我有Demo
沒有,其實文章中貼出的代碼組合起來就是個Demo
。後來想了想,仍是給本系列文章配了一個簡單的Demo
,方便你們運行調試,後續會給全部博客的文章都加上Demo
。
Demo
只是來輔助讀者更好的理解文章中的內容,應該博客結合Demo
一塊兒學習,只看Demo
仍是不能理解更深層的原理。Demo
中幾乎每一行代碼都會有註釋,各位能夠打斷點跟着Demo
執行流程走一遍,看看各個階段變量的值。
Demo地址:劉小壯的Github
這兩天更新了一下文章,將CoreData
系列的六篇文章整合在一塊兒,作了一個PDF
版的《CoreData Book》,放在我Github上了。PDF
上有文章目錄,方便閱讀。
若是你以爲不錯,請把PDF幫忙轉到其餘羣裏,或者你的朋友,讓更多的人瞭解CoreData,衷心感謝!