本文由海水的味道編譯整理,請勿轉載,請勿用於商業用途。
當前版本號:0.4.0
第二章 Core Data入門
本章將講解Core Data框架中涉及的基本概念,以及一個簡單的Core Data app的結構組成。
首先回憶一下,在你還沒有使用Core Data之前,你是如何處理數據的持久化。
將對象持久化到磁盤
當你需要在程序中將數據保存到磁盤,通常你會創建一個對象容器,可能是數組、集合或字典。當在保存數據時,你會將對象編碼或序列化,然后保存到二進制文件。對於數據量較小的情況,你可能也會選擇.plist文件保存數據。
作為一種將數據保存到二進制文件的替代方法,在Core Data引入iOS之前,開發者也可以直接使用SQLite,它是一個簡單且非常輕量級的數據庫。SQLite自iPhone OS的早期版本開始就可用於iOS設備。當你在開發中需要使用包含大量對象的容器時,保存數據到數據庫對數據的查詢速度非常有幫助。
SQLite,顧名思義,它是基於結構化查詢語言SQL。通過發送如insert或select(提取)SQL指令與數據庫進行進行交流。不需擔心提取某個對象而導致整個二進制文件都會載入內存的問題。
使用SQLite的不足時是:你需要使用大量的過程式C語言API,編寫繁復的數據訪問代碼。為了保存一個對象到SQLite數據庫,你需要編寫包含INSERT語句的字符串的代碼。該字符串由對象的實例變量值組成,而在傳入字符串到最終的C語言函數之前,還需要將字符串轉換成C語言風格的字符串。
遇見Core Data
相對於SQLite,Core Data集合了面向對象數據庫在對象序列化方面的速度與效率的優勢。
實體與NSManagedObject對象
在傳統的數據庫中,數據表定義了數據的結構;在面向對象語言中,類描述了數據的結構;在Core Data框架中,描述數據的結構則是實體。一個實體由屬性和關系組成。你通過使用Xcode的數據模型設計器創建實體,並為實體指定屬性和關系。你可以通過如下方式實體創建一個NSManagedObject對象。
NSEntityDescription *patientEntity = [NSEntityDescription entityForName:@"Patient" inManagedObjectContext:context];
NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:patientEntity insertIntoManagedObjectContext:context];
對於NSManagedObject對象,你可以使用KVC方式訪問對象的屬性,類似代碼清單2.1所示。
代碼清單2.1 訪問一個NSManagedObject對象的屬性
NSManagedObject *aPatientObject; // 假設對象已填充正確數據
NSString *firstName = [aPatientObject valueForKey:@"firstName"];
NSString *lastName = [aPatientObject valueForKey:@"lastName"];
[aPatientObject setValue:@"Pain killers" forKey:@"currentMedication"];
[aPatientObject setValue:@"Headache" forKey:@"currentIllness"];
你也可以使用一個暴露了訪問器方法或屬性的自定義NSManagedObject子類。這樣你就可以使用類似於代碼清單2.2那樣的方式訪問屬性。你將在第六章“使用NSManagedObject對象”中了解到更多的細節。
代碼清單2.2 使用一個自定義的NSManagedObject子類。
Patient * aPatientObject; // 假設對象已填充正確數據
NSString *firstName = [aPatientObject firstName];
NSString *lastName = aPatientObject.lastName;
[aPatientObject setCurrentMedication:@"Pain killers"];
aPatientObject.currentIllness = @"Headache";
你仍然可以使用valueForKey:的形式訪問對象屬性,但是使用KVC要比使用訪問器效率低一點。 只在必要時使用KVC,比如你需要動態選擇key或keyPath。
[newEmployee setValue:@”Stig” forKey:@”firstName”];
[aDepartment setValue:@1000 forKeyPath:@”manager.salary];
NSManagedObjectC對象上下文(Managed Object Contexts)
當你使用NSManagedObject對象,實際上你是在一個特定的上下文中進行操作,該上下文被稱為NSManagedObject對象上下文。上下文就像一個容器,容器內存放這從持久化存儲文件中載入的NSManagedObject對象。上下文時刻保持對容器內的NSManagedObject對象變化的的追蹤。
從概念上講,上下文好比是桌面平台的一個文檔對象,文檔負責顯示存儲在磁盤上的數據。當文檔被打開,數據將會從磁盤上被加載並顯示在屏幕上。上下文保持對文檔變化的追蹤。文檔數據被上下文持有在內存中,當數據被保存,上下文負責將變化寫入到磁盤。
NSManagedObject對象上下文(Managed Object Context 簡稱MOC)以類似的方式工作。當需要數據時,它負責從存儲區中提取(fetch)數據。並在內存中時刻保持對這些對象的變化的追蹤,最后當它們被保存到磁盤時,MOC將這些改變寫入磁盤。除非你命令MOC執行保存操作,否則你在上下文中對任何NSManagedObject對象所做的任何改變都是臨時的,並不會影響到磁盤上的數據。
與常規的文檔對象不同,在某一時刻,你可以使用不止一個NSManagedObject對象上下文。即使它們都關聯於同一個底層數據。比如你可能加載同一位病人對象到兩個不同的上下文中。並對其中一個對象進行修改。如圖2.2所示。另一個對象的上下文將不會受這些修改的影響。除非你選擇保存第一個上下文,此時將會有一個通知被發送以提醒你另一個上下文已經修改了數據。如若需要,你可以重新加載第二個上下文。
圖2.2 NSManagedObject對象上下文和它的NSManagedObject對象
雖然在iOS平台多個上下文的使用情況要比桌面平台少見,如果你在后台處理NSManagedObject對象,例如從在線資源獲取數據並保存到app的本地數據存儲區,那么你就需要使用一個單獨的上下文;如果你需要使用NSManagedObject對象上下文提供的自動撤銷功能,你也需要使用一個單獨的上下文處理這些需要撤銷的NSManagedObject對象。撤銷對單個屬性的所有改變被看作是一次單獨的動作。當保存對象到主上下文時,保存所有這些改變的行為將被算做是主上下文中的一次撤銷動作。當用戶需要時,允許用戶一次性撤銷所有的改變。你將在隨后的章節看到這樣的案例。
NSPersistentStore與NSPersistentStoreCoordinator
底層數據會被持有在磁盤的一個持久化存儲文件中。對於iOS設備,通常指SQLite存儲區。你也可以選擇使用二進制存儲方式或你自己定義的原子存儲方式,但這要求整個對象圖被載入到內存。在內存有限的設備上,這很快會是一個問題。
你從不需要直接和持久化存儲文件交流,你不需要擔心如何存儲數據。相反,你通過NSPersistentStoreCoordinator對象存儲數據。你可以把NSPersistentStoreCoordinator看做是一個數據庫連接。一個NSPersistentStoreCoordinator對象被作為NSManagedObject對象上下文的中介;可能一個NSPersistentStoreCoordinator對象要與多個持久化存儲文件交互。這意味着NSPersistentStoreCoordinator對象要將這些持久化存儲文件聯合在一起,以暴露給NSManagedObject對象上下文訪問。
如圖2.3所示。一個NSPersistentStoreCoordinator對象包含了一個持久化存儲文件。當你設置了一個NSPersistentStoreCoordinator對象,你通常會選擇SQLite作為存儲文件的存儲類型。除了SQLite儲存類型以外,你還可以選擇二進制存儲類型,XML存儲類型以及駐內存存儲類型。需要注意的是二進制和XML存儲類型都是原子性的,這意味着即使你只是修改了少量數據,在保存數據時,你也需要將整個文件寫入磁盤。相對的,當你讀取數據時,也需要將整個數據文件讀入內存。
通常你不需要過多的擔心持久化存儲文件和 NSPersistentStoreCoordinator 對象 ,除非你想處理多個存儲區或定義你自己的存儲方式。圖2.3 Core Data結構
通常你不需要過多的擔心持久化存儲文件和NSPersistentStoreCoordinator對象,除非你想處理多個存儲區或定義你自己的存儲方式。
NSManagedObjectModel對象
在圖2.3中,一個NSManagedObjectModel對象處在NSPersistentStoreCoordinator對象和NSManagedObject對象上下文之間。Core Data根據NSManagedObjectModel對象確定如何將底層的持久化文件中的數據映射為NSManagedObject對象。一個NSManagedObjectModel對象用於表示數據的結構。NSManagedObjectModel對象也被稱為對象圖(object graph)。
關系
數據模型設計器也是定義實體間關系的地方。比如一個Patient對象會有一個指向Doctor對象的一對一關系,同樣,Doctor對象也會有一個指向Patient對象的一對多關系。如圖2.1所示。
圖2.1 病人和醫生間的關系
當對關系進行建模時,通常會使用到關系型數據庫的術語,比如一對一、一對多或多對多。在圖2.1中,一位病人由一位醫生負責,而一位醫生需要負責多位病人,所以醫生-病人的關系是一對多。
如果醫生-病人關系是一對多的,那么反轉(inverse)關系(病人-醫生)就是多對一。當你在數據模型設計器中建模這些關系時,這兩種正反關系你都需要顯示地設置。通過顯式地設置反轉關系,Core Data就會自動地維護數據的完整性。如果你給一位病人指派了一位醫生,這位病人也將被自動的添加到醫生的病人列表中。
為每一個關系指定一個名字,以使得關系能夠和實體的屬性那樣被訪問。同樣,你也可以使用KVC方法或者在自定義子類中聲明的訪問器和屬性來訪問關系,類似代碼清單2.3那樣。
代碼清單2.3 訪問對象關系
Patient *aPatientObject; // 假設對象已填充正確數據
Doctor *aDoctorObject = [aPatientObject valueForKey:@"doctor"];
Patient *anotherPatientObject;
anotherPatientObject.doctor = aDoctorObject;
NSLog(@"Doctor's patients = %@", [aDoctorObject patients]);
/* 輸出
Doctor's patients = (aPatientObject, anotherPatientObject, etc...)
*/
需要注意一點: Core Data不會維護任何存放對象的集合(NSArray,NSSet,NSDictonary)內元素的次序,包括一對多關系。稍后你將在書中看到對象返回的次序可能不會和輸入的次序一樣。如果次序非常重要,你需要自己來追蹤次序,可以為每個對象設置一個升序的數值索引屬性。如果你使用過MySQL,PostgreSQL或MS SQL Server數據庫,你可能給每個數據庫中的記錄一個唯一id。而使用Core Data,你不需要任何類型的唯一標識Id,也不需要處理表連接。Core Data將在后台自動處理。你所需要做的就是定義對象間的關系。Core Data框架將在后台決定如何生成最佳的底層機制。
對於醫生-病人的一對多關系,如果你想往關系patients集合中加入一個Patient對象。你可以使用如下代碼:
NSMutableSet *patients = [aDoctor mutableSetValueForKey:@”patients”];
[patients addObject:newPatient];
[patients removeObject:oldPatient];
// 或
[aDoctor addPatientObject:newPatient];
[aDoctor removePatientObject:oldPatient];
不要使用點語法獲取集合,因為點語法獲得的值是NSSet,而不是NSMutableSet類型:
[aDoctor patients] addObject:newPatient];
[aDoctor.patients addObject:newPatient];
提取對象
NSManagedObject對象上下文也是你使用NSFetchRequest對象從磁盤提取對象的媒介。一個NSFetchRequest對象必須包含一個NSEntityDescription對象,用來指定哪個實體的對象需要被檢索。如果你想從持久存儲區中提取所有的病人記錄,你需要創建一個NSFetchRequest對象,指定Patient實體為被檢索對象,並告訴MOC執行提取請求。MOC返回一個數組形式的結果給你。同樣,數組內元素的次序可能與你存儲時的次序不一致,也可能你下一次執行提取請求得到的結果和現在得到的結果次序也不一樣,除非你對提取到結果按特定的規則再做一次排序。
為了提取特定的或滿足特定條件的對象,你可以為NSFetchRequest對象配置一個NSPredicate對象;為了使用特定次序對結果進行排序,你可以再為NSFetchRequest對象配置一個NSSortDescriptor對象數組。你可能選擇獲取某位醫生的所有病人記錄,並按lastName屬性進行排序。如果你在排序之前為所有的病人對象設置了一個數值索引屬性,你可以要求提取結果按索引屬性進行排序,以使得每次它能以相同的次序返回。
惰性加載(Faulting)與唯一性
Core Data使用了一種稱為惰性加載(faulting)的技術,盡力優化性能並保持最小的內存使用率。
fault是一個很讓人迷惑的一個詞匯,蘋果官方文檔對Fault的定義:一個fault是一個占位符對象,用於表示一個還沒有被完整實例化的NSManagedObject對象或是一個代表關系的容器對象。所以Core Data中的fault更接近於Page Faults的概念。翻譯為惰性加載並不妥當,但是相對比較形象。
考慮這樣的情形:如果你加載了一個Patient記錄到內存中;為了訪問該Patient對象關聯的Doctor關系,Doctor對象也會被加載。如果你又需要訪問該Doctor對象負責的所有其他病人,那么所有與該Doctor對象關聯的Patient對象就會全部加載到內存中。伴隨這樣的行為,提取單個對象的行為最終會演變成提取成百上千的對象——每一個相關聯的對象都會被提取,直至可能整個數據庫被載入內存。
為了解決這個問題,Core Data將返回給你的NSManagedObject對象的屬性和關系都標記為惰性(fault),這樣對象的實際數據就不會被載入到內存。如果你嘗試訪問其中一個屬性或關系,比如訪問某位病人的醫生名字,惰性將被消除,Core Data會為你提取期望對象的值。類似地,最新提取到的Doctor對象的關系也會被設置為惰性,當你需要訪問該關系指向的任何相關的對象,惰性就會被消除。所有這些都是自動發生的,不需要讓你擔憂。當然,你也可以將那些暫時不用的對象重新打回惰性狀態以減少內存占用。
在代碼調試中,你可能需要查看提取到的結果集中是否存在特定的對象。如圖2.2所示。因為Core Data會啟用惰性加載機制。所以你得到的會是類似圖2.2所示的結果。
圖片顯示較小,可拖拽到桌面查看:-]
圖2.2 默認的惰性機制
為了查看結果,你可以禁用Core Data默認返回惰性對象的設置。使用如下代碼:
request.returnsObjectsAsFaults = NO;
為了保持代碼的整潔,根據上述代碼設置一個斷點動作,如圖2.3所示。
圖2.3 強制返回結果為非惰性
上述設置只是針對屬性,如果你有一個關系,使用如上方法,你將得到一個“relationship fault”。
所以為了查看關系的值,你可以使用類似下述代碼:
p [team.members count]
這將消除關系的惰性狀態。如果你想禁用關系的默認的惰性設置,以獲得預提取的效果。使用如下代碼:
[request setRelationshipKeyPathsForPrefetching:@[@"team"]];
當一個NSManagedObject對象已經被載入,那么NSManagedObject對象所處的當前上下文會確保在隨后的提取操作中,總是返回已經存在的實例,考慮代碼清單2.4。
代碼清單2.4 提取唯一對象
Patient *firstPatient;
Doctor *firstPatientsDoctor = firstPatient.doctor;
Patient *secondPatient;
Doctor *secondPatientDoctor = secondPatient.doctor;
/*
* 如果兩個病人對應的醫生都是同一人,那么當各個病人關系上的惰性被消除后,返回的醫生實例都會是同一個實例。
*/
if (firstPatientsDoctor == secondPatientsDoctor) {
NSLog(@"Patients share a doctor!");
}
這被稱為唯一性。比如訪問一個特定的Patient對象,在任何NSManagedObject對象上下文中,你都只會得到同一個對象實例。
一個NSManagedObject對象關聯一個上下文。在一個給定的上下文中,一個NSManagedObject對象表示持久化存儲文件中的一條記錄。在一個給定的上下文中,持久化存儲文件中的一條記錄只對應一個NSManagedObject對象,但有可能存在多個上下文,每個上下文都有一個關聯同一個存儲記錄的NSManagedObject對象。換句話說,一個NSManagedObject對象和一條存儲記錄之間的關系是一對一的;一個存儲區記錄和NSManagedObject對象之間的關系是多對一的。
研究Xode內置的Core Data模板
現在你已經對Core Data術語有了一個很好的概念,接下來,我們將學習如何利用XCode提供的內置Core Data模板構建一個基於Core Data的iOS app。
基於導航的項目模板
Xcode中預置Core Data配置的項目模板有:Master-Detail Application,Utility Application以及Empty Application。
圖2.4 新建項目的窗口
啟動XCode,選擇File>New>New Project(Shift+Command+N快捷鍵),接着選擇Master-Detail Application模板,項目取名為TemplateProject,然后勾選Use Core Data復選框,如圖2.4所示。
勾選“Use Core Data”選項后項目會多出一些額外項。首先,項目引用了CoreData.framework,並創建了一個TemplateProject.xcdatamodeld文件,該文件定義了數據模型結構,你可以使用XCode內置的可視化建模工具進行構建。
點擊TemplateProject.xcdatamodeld文件。在XCode中有兩種方式查看數據模型,Table和Graph方式,如圖2.5所示。
圖2.5 Xcode數據模型設計器的兩類編輯風格
數據模型設計器
在編輯器的左上方,你將看到一個實體列表。在模板文件中,有一個Event的實體。如果你使用Table編輯器風格顯示選擇的實體,你會看到實體的Attributes列表,Relationships列表以及Fetched Properties列表。
Event實體列出了一個叫做timeStamp屬性。如果你點擊選擇這個屬性,然后打開XCode 的數據模型檢視器(Data Model inspector)(按住Option+Command+3快捷鍵)。你將看到它的Type被設置成了Date。
檢視器提供了一些關於屬性設置的選項。比如你可以選擇在數據存儲時驗證數據,或者讓屬性為可選;這些選項會在第三章“數據建模”進行討論。
XCode的Graph編輯器風格對你的對象模型實體進行了可視化呈現。現在還只有一個實體,當實體數量不止一個,實體之間可以使用線和箭頭進行關系連接。如圖2.6所示。
圖2.6 對象圖中對象之間的關系
設置Core Data棧
當使用持久化存儲文件中的數據時,你需要構建一個對象棧;棧的底部是實際存放在磁盤上的持久化存儲文件,接着是NSPersistentStoreCoordinator對象,它將持久化存儲文件和它的下一級NSManagedObject對象上下文連接在了一起。如圖2.7所示。
圖2.7 Core Data棧
在NSPersistentStoreCoordinator對象的底部可能有多個持久化存儲文件和多個NSManagedObject對象上下文。
讓我們來研究一下用來設置Core Data棧的模板代碼。打開AppDelegate.h文件,你會發現文件內定義了一些屬性聲明。代碼清單2.5所示。
代碼清單2.5 AppDelegate.h中設置Core Data棧的模板代碼
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
app代理負責保持對NSManagedObjectModel對象的追蹤,NSManagedObjectModel對象模型的信息包含在TemplateProject.xcdatamodeld文件中[1]。AppDelegate有一個指向NSPersistentStoreCoordinator對象的引用,以及一個指向NSManagedObject對象上下文的引用。applicationDocumentsDirectory被用於確定數據保存的磁盤位置。
切換到AppDelegate.m文件,滾動到文件的底部,你會看到一個applicationDocumentsDirectory方法,該方法返回app的documents目錄的路徑。
接下來找到persistentStoreCoordinator方法。為了訪問documents目錄中的SQLite存儲文件TemplateProject.sqlite,該方法設置了一個NSPersistentStoreCoordinator對象,並使用managedObjectModel方法提供的模型來初始化持久存儲。
接下來找到managedObjectModel方法,該方法返回一個根據TemplateProejct.momd的文件創建的NSManagedObjectModel對象。當你編譯項目時,TemplateProject.xcdatamodeld數據模型將被編譯成.momd資源,並且保存到app的Bundle目錄。
通過使用NSManagedObjectModel的類方法mergedModelFromBundles方法合並所有可用的模型文件或使用modelByMergingModels:方法合並特定的文件來創建一個NSManagedObjectModel都是可以的。雖然在當前項目中只有一個模型文件,但是為了對模型進行分類管理而創建多個.xcdatamodeld文件是可行的。
找到managedObjectContext方法。該方法使用persistentStoreCoordinator方法返回的NSPersistentStoreCoordinator對象配置上下文。現在你在application: didFinishLaunchingWithOptions:方法中,只需通過點語法self.managedObjectContext就可調用managedObjectContext方法,並返回你需要的上下文。而隨着方法的調用,NSManagedObjectModel對象、NSPersistentStoreCoordinator對象以及NSManagedObject對象上下文,三者形成一種多米諾骨牌效應。
最后,applicationWillTerminate:方法調用了saveContext方法,saveContext方法檢查在NSManagedObject對象上下文中對象所發生的任何變化,如果有變化就嘗試保存。這意味着當程序退出時,持久化存儲文件將被來自上下文的變化所更新。一些不同版本的項目模板在applicationDidEnterBackground:方法中調用saveContext方法。
你可能不需要去更改這些方法中的代碼,除非你需要使用多個存儲區或自定義存儲方式。一旦NSManagedObject對象上下文被設置,它就被傳遞給需要執行存儲或提取任務的視圖控制器,並將提取的數據顯示在視圖上,這也是你一般使用Core Data寫的最多的一種代碼。在第四章——“存儲和提取數據基礎“,你將開始構建一個使用Core Data提供顯示數據的表格視圖。
運行程序
為了了解模板項目的功能,構建運行程序。你將發現你可以添加一個Event列表;表視圖將顯示事件的timeStamp屬性。注意你可以從表視圖中使用Edit按鈕移除這些項目,或者向左滑動手勢刪除條目。
快速過一遍RootViewController中的代碼
為了了解這一切運轉的原理,打開RootViewController.m實現文件。TemplateProject使用了NSFetchedResultsController對象來簡化對提取結果和表格視圖的處理。NSFetchedResultsController對象被惰性創建並只在表格視圖數據源方法有需要時才提取數據。
圖2.8 運行在模擬器中的Template app
找到fetchedResultsController方法,你會看到在NSFetchRequest對象的配置中,使用了Event實體,並提供了一個NSSortDescriptor對象以讓提取結果按timeStamp進行排序。
在表格視圖中顯示內容的是標准表格視圖數據源方法;對於numberOfSections和numberOfRows方法,模板代碼只是查詢NSFetchedResultsController對象。cellForRowAtIndexPath:方法為了顯示數據,使用了一個configureCell:atIndexPath:方法。注意到獲得指定的索引持有的NSManagedObject對象是多么簡單的一件事。比如顯示時間戳的代碼,見代碼清單2.6。
代碼清單2.6 在單元格中顯示timeStamp
NSManagedObject *managedObject = [self.fetchedResultsControleler objectAtIndexPath:indexPath]; // 獲得指定索引持有的NSManagedObject對象
cell.textLabel.text = [[managedObject valueForKey:@"timeStamp"] description];
NSFetchedResultsController對象返回了指定索引的對象,接着查詢返回對象的timeStamp鍵的描述。
commitEditingStyle:forRowAtIndexPath:方法簡單的告訴NSManagedObject對象上下文刪除在指定NSIndexPath上的對象,接着命令上下文保存。這將意味着對象從表視圖被刪除后也會從持久存儲區中被刪除。
最后,找到insertNewObject方法,當用戶嘗試加入一個對象到表視圖時,該方法將被調用。接着你將看到如下的處理過程:
Ø 獲得一個NSManagedObject對象上下文指針;
Ø 決定創建新對象的實體;
Ø 插入一個新的實體對象到NSManagedObject對象上下文;
Ø 對新創建的NSManagedObject對象設置屬性值
Ø 命令上下文執行保存。
當上下文執行保存,新的對象將被寫到持久存儲區中。這是如此簡單!
訪問.sqlite文件內容
當你在表格視圖中插入了一些時間戳記錄,為了驗證這些數據是否成功保存到sqlite文件。首先在Finder中打開app在模擬器的安裝目錄:~/Library/Application Support/iPhone Simulator/[OS version]/Applications/[appGUID]/。進入Documents文件夾,目錄內包含了三個文件TemplateProject.sqlite、TemplateProject.sqlite-shm、TemplateProject.sqlite-wal。如上文所述,根據AppDelegate.m文件中的persistentStoreCoordinator方法可知,時間戳記錄被存儲在TemplateProject.sqlite文件中。為了查看文件中的內容,可以使用Firefox的插件SQLite Manager打開.sqlite文件。但是當你打開.sqlite文件,里面並沒有出現你之前添加的時間戳記錄!
所以當你在iOS 7中只是拷貝.sqlite文件來實現對數據的備份,很可能將引起數據的丟失和不一致。推薦的做法是使用如下NSPersistentStoreCoordinator類的方法而不是使用文件系統API,實現Core Data存儲文件的備份和恢復。
- (NSPersistentStore *)migratePersistentStore:(NSPersistentStore *)store toURL:(NSURL *)URL options:(NSDictionary *)options withType:(NSString *)storeType error:(NSError **)error
你也可以通過代碼清單2.7,將-wal日志模式改為回滾日志模式:
代碼清單2.7 禁用日志模式
NSDictionary *options = @{NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}};
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
// 錯誤處理
}
當禁用-wal模式之后,再次運行程序,回滾日志模式會強制Core Data執行一個檢查點操作,-wal中的數據將會被被合並到主存儲文件中,之后TemplateProject.sqlite-wal會被刪除。再次使用SQLite Manager打開TemplateProject.sqlite文件,之前插入的時間戳記錄終於出現了。如果你既想使用默認的-wal模式,也希望不通過禁用日志模式這種繁瑣的操作實現查看.sqlite文件中的內容,此時你可以使用sqlite3的命令行實現強制執行檢查點,將-wal文件中的內容合並到.sqlite文件中。代碼如下:
蘋果公司之所以改變默認的日志模式,是基於性能的考慮,使用-wal模式會有更好的性能。在數據讀取的頻率比數據寫入高的場景中,-wal模式比傳統的回滾日志模式慢1%-2%,但是使用-wal模式在絕大多數情況下會更快。WAL支持iOS 4+和OS X 10.7+,所以你需要在iOS 7以下版本中使用WAL,可以使用代碼清單2.8所示,開啟WAL支持。
代碼清單2.8 開啟WAL模式
@{ NSSQLitePragmasOption:@ "journal_mode = WAL" }
總結
Core Data框架基本的5個類::NSPersistentStoreCoordinator、NSManagedObjectContext、NSManagedObjectModel、NSEntityDescription、NSManagedObject。
Ø NSPersistentStoreCoordinator持久化存儲協調器(簡稱協調器):負責從磁盤加載數據和將數據寫入磁盤。協調器可以處理多種格式的數據庫文件(NSPersistentStore),如二進制文件,XML文件、SQLite文件。你也可以實現自己的數據庫文件格式(使用NSAtomicStore和NSIncrementalStore類),理論上你可以實現打開World或Photoshop文件的協調器。
Ø NSEntityDescription實體描述(簡稱實體):實體可以被看做是NSManagedObject對象的“class”。實體定義了一個NSManagedObject對象所擁有的所有屬性(NSAttributeDescription),關系(NSRelationshipDescription),提取屬性(NSFetchedPropertyDescription)。
Ø NSManagedObjectContext托管對象上下文(簡稱上下文):上下文是內存中的一塊暫存區域。查詢對象(使用NSFetchRequest),創建對象,刪除對象等操作都是在上下文中進行。在上下文沒有保存之前,對數據的任何修改都只記錄在暫存區中,不會影響磁盤上的數據。你可以創建多個上下文,但整個程序只能創建一個NSPersstentStoreCoordinator對象。
Ø NSManagedObject托管對象:Core Data的核心單元。模型對象的數據被持有在NSManagedObject對象中。每一個NSManagedObject對象都對應一個實體(就像每一個對象都有一個類)
Ø NSManagedObjectModel托管對象模型:NSManagedObjectModel通常被定義在一個.mom文件中,文件中保存了所有實體的定義。NSManagedObjectModel and the NS*Description 類完整定義了Core Data模型應該/可以包含的內容。
練習
1. 閱讀補充資源:http://www.drdobbs.com/database/understanding-core-data-on-ios/240004648?pgno=1
2. 重新創建一個新的項目,在創建項目時選擇不勾選Use Core Data選項。參照TmplateProject項目,在新的項目中實現對Core Data功能的支持。
[1]譯注:模型文件TemplateProject.xcdatamodeld實際上是一個包(Package)。包內包含.xccurrentversion和TemplateProject.xcdatamodel。TemplateProject.xcdatamodel也是也一個包,包內包含contents文件。