《駕馭Core Data》 第二章 Core Data入門

本文由海水的味道編譯整理,請勿轉載,請勿用於商業用途。html

   當前版本號:0.4.0ios

章 Core Data入門sql

本章將講解Core Data框架中涉及的基本概念,以及一個簡單的Core Data app的結構組成。數據庫

         首先回憶一下,在你尚未使用Core Data以前,你是如何處理數據的持久化數組

將對象持久化到磁盤併發

當你須要在程序中將數據保存到磁盤,一般你會建立一個對象容器,多是數組、集合或字典。當在保存數據時,你會將對象編碼或序列化,而後保存到二進制文件。對於數據量較小的狀況,你可能也會選擇.plist文件保存數據。app

做爲一種將數據保存到二進制文件的替代方法,在Core Data引入iOS以前,開發者也能夠直接使用SQLite,它是一個簡單且很是輕量級的數據庫。SQLiteiPhone OS的早期版本開始就可用於iOS設備。當你在開發中須要使用包含大量對象的容器時,保存數據到數據庫對數據的查詢速度很是有幫助。框架

SQLite,顧名思義,它是基於結構化查詢語言SQL。經過發送insertselect(提取)SQL指令與數據庫進行進行交流。不需擔憂提取某個對象而致使整個二進制文件都會載入內存的問題。編輯器

使用SQLite的不足時是:你須要使用大量的過程式C語言API,編寫繁複的數據訪問代碼。爲了保存一個對象到SQLite數據庫,你須要編寫包含INSERT語句的字符串的代碼。該字符串由對象的實例變量值組成,而在傳入字符串到最終的C語言函數以前,還須要將字符串轉換成C語言風格的字符串。 函數

碰見Core Data

相對於SQLiteCore 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,好比你須要動態選擇keykeyPath

[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對象。撤銷對單個屬性的全部改變被看做是一次單獨的動做。當保存對象到主上下文時,保存全部這些改變的行爲將被算作是主上下文中的一次撤銷動做。當用戶須要時,容許用戶一次性撤銷全部的改變。你將在隨後的章節看到這樣的案例。

NSPersistentStoreNSPersistentStoreCoordinator

底層數據會被持有在磁盤的一個持久化存儲文件中。對於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 病人和醫生間的關係

當對關係進行建模時,一般會使用到關係型數據庫的術語,好比一對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不會維護任何存放對象的集合(NSArrayNSSetNSDictonary)內元素的次序,包括一對多關係。稍後你將在書中看到對象返回的次序可能不會和輸入的次序同樣。若是次序很是重要,你須要本身來追蹤次序,能夠爲每一個對象設置一個升序的數值索引屬性。若是你使用過MySQLPostgreSQLMS 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 DataiOS app

基於導航的項目模板

Xcode中預置Core Data配置的項目模板有:Master-Detail ApplicationUtility 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中有兩種方式查看數據模型,TableGraph方式,如圖2.5所示。

2.5  Xcode數據模型設計器的兩類編輯風格

數據模型設計器

在編輯器的左上方,你將看到一個實體列表。在模板文件中,有一個Event的實體。若是你使用Table編輯器風格顯示選擇的實體,你會看到實體的Attributes列表,Relationships列表以及Fetched Properties列表。

Event實體列出了一個叫作timeStamp屬性。若是你點擊選擇這個屬性,而後打開XCode 的數據模型檢視器(Data Model inspector(按住Option+Command+3快捷鍵)。你將看到它的Type被設置成了Date

檢視器提供了一些關於屬性設置的選項。好比你能夠選擇在數據存儲時驗證數據,或者讓屬性爲可選;這些選項會在第三章「數據建模」進行討論。

XCodeGraph編輯器風格對你的對象模型實體進行了可視化呈現。如今還只有一個實體,當實體數量不止一個,實體之間可使用線和箭頭進行關係鏈接。如圖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方法,該方法返回appdocuments目錄的路徑。

接下來找到persistentStoreCoordinator方法。爲了訪問documents目錄中的SQLite存儲文件TemplateProject.sqlite,該方法設置了一個NSPersistentStoreCoordinator對象,並使用managedObjectModel方法提供的模型來初始化持久存儲。

接下來找到managedObjectModel方法,該方法返回一個根據TemplateProejct.momd的文件建立的NSManagedObjectModel對象。當你編譯項目時,TemplateProject.xcdatamodeld數據模型將被編譯成.momd資源,而且保存到appBundle目錄。

經過使用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進行排序。

在表格視圖中顯示內容的是標準表格視圖數據源方法;對於numberOfSectionsnumberOfRows方法,模板代碼只是查詢NSFetchedResultsController對象。cellForRowAtIndexPath:方法爲了顯示數據,使用了一個configureCellatIndexPath:方法。注意到得到指定的索引持有的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.sqliteTemplateProject.sqlite-shmTemplateProject.sqlite-wal。如上文所述,根據AppDelegate.m文件中的persistentStoreCoordinator方法可知,時間戳記錄被存儲在TemplateProject.sqlite文件中。爲了查看文件中的內容,可使用Firefox的插件SQLite Manager打開.sqlite文件。可是當你打開.sqlite文件,裏面並無出現你以前添加的時間戳記錄!

         緣由是蘋果公司在 iOS 7 OS X Mavericks 中修改了 Core Data SQLite 存儲文件的默認日誌模式。新的日誌模式爲 Write-Ahead Logging (WAL) 。WAL每次支持多個併發讀取和一個併發寫入。在 WAL 模式下,當你提交事務, Core Data 將保持主存儲文件( .sqlite 文件)不改變,而將事務追加到同一目錄下的 -wal 文件中。在 Core Data 的上下文被保存, -wal 文件並不會被刪除, -wal 文件中的數據也不會合併到主存儲文件中,當wal文件中的數據積累到幾兆字節數據後,SQLite 將自動執行一個檢查點操做(同步wal文件和主存儲文件的行爲被稱爲檢查點) ,wal文件中的內容將會合併到主存儲文件中,接着你可能會看到wal文件被刪除了,但只不過是事務一個至關短暫的狀態,常規狀況下wal文件都會出現。而在 iOS 6.x Mountain Lion 中,默認的日誌模式是回滾日誌模式 ,Core Data 會建立一個 -journal 文件來臨時保存事務。當上下文被保存,主存儲文件會被更新, -journal 文件也會被刪除,所以主存儲文件包含的是最新的數據記錄。

因此當你在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個類::NSPersistentStoreCoordinatorNSManagedObjectContextNSManagedObjectModelNSEntityDescriptionNSManagedObject

Ø   NSPersistentStoreCoordinator持久化存儲協調器(簡稱協調器):負責從磁盤加載數據和將數據寫入磁盤。協調器能夠處理多種格式的數據庫文件(NSPersistentStore),如二進制文件,XML文件、SQLite文件。你也能夠實現本身的數據庫文件格式(使用NSAtomicStoreNSIncrementalStore類),理論上你能夠實現打開WorldPhotoshop文件的協調器。

Ø   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)。包內包含.xccurrentversionTemplateProject.xcdatamodelTemplateProject.xcdatamodel也是也一個包,包內包含contents文件。

相關文章
相關標籤/搜索