iOS-數據持久化-CoreData

CoreData詳解git

介紹:程序員

Cocoa環境下,若是你想使用數據庫(如sqlite),你可使用sql語句的方式經過相關的工具類進行數據庫的直接操做。固然你也能夠經過別人封裝以後的一些簡單框架,使得你的操做更加簡單(如FMDB BNRPersistence)。github

 

Cocoa框架自己提供了CoreData這個API可方便的讓開發者經過操做對象的方式在操做數據庫。CoreData是一個對象圖(object graph)以及持久化的管理框架。咱們能夠經過CoreData創對象,設置好象之間的關係,而後將其持久化(咱們甚至可使用內存數據庫),或者從硬盤上將持久化後的數據加載到內存中。對象圖,咱們能夠建立一個個的對象,並維持不一樣對象之間的關係,一對一,一對多等。web

 

CoreData有大量的特性,諸如支持Redo,Undo的功能,這些不少Document based的程序中顯得很是的有用。提供數據model結構變化輕量級的遷移方案。CoreData還經過Binding特性和控件的緊密結合,這樣使得只須要少許的代碼即可以完成強大的功能,下面是一個例子正則表達式

http://www.timisted.net/blog/archive/multiple-windows-with-core-data/sql

 

存儲方式

Core Data能夠將數據存儲爲XML,二進制文件或SQLite文件。在Mac OS X 10.5 Leopard及之後的版本中,開發者也能夠經過繼承NSPersistentStore類以建立自定義的存儲格式。每種方法都有其優缺點,例如XML的可讀性,SQLite的節約空間等。數據庫

 

Core Data的這一方面相似於原始的Enterprise Objects Framework(EOF)系統,但EOF中開發者可使用相對簡潔的查詢方式,而在Core Data中,只能使用一個語法相似SQL子集的查詢語言,稱爲Predicate。Core Data是標準化的,能夠自由的讀寫Xcode數據模型文件(一般是.xcdatamodel文件)。編程

 

EOF不一樣,Core Data目前沒有設計多用戶或多線程訪問模式。模型遷移一般也須要代碼,若其它開發者依賴於某個數據模型,則該數據模型的設計者可能在模型發生改變時須要與新數據模型一塊兒提供版本轉換代碼。windows

 

操做簡介

Core Data由相對龐大的類繼承體系組成,但開發者須要關注的接口只是其中的一個相對小的子集。數組

 

 

 

通常須要定義如下Core Data的三個必備

NSPersistentStoreCoordinator *persistentStoreCoordinator;

NSManagedObjectModel *managedObjectModel;

NSManagedObjectContext *managedObjectContext;

以及使用時須要用到的

NSFetchedResultsController *fetchedResultsController;

使用步驟:

 

還記得咱們每次使用CoreData的時候系統都會給咱們建立一些代碼嗎?

複製代碼
 1 #pragma mark - Core Data 堆棧
 2 //返回 被管理的對象上下文
 3 - (NSManagedObjectContext *)managedObjectContext
 4 {
 5     if (_managedObjectContext) {
 6         return _managedObjectContext;
 7     }
 8     
 9     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
10     if (coordinator) {
11         _managedObjectContext = [[NSManagedObjectContext alloc] init];
12         [_managedObjectContext setPersistentStoreCoordinator:coordinator];
13     }
14     return _managedObjectContext;
15 }
16 
17 // 返回 持久化存儲協調者
18 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
19 {
20     if (_persistentStoreCoordinator) {
21         return _persistentStoreCoordinator;
22     }
23     
24     NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataNotes.sqlite"];
25     
26     _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
27     
28     [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
29                       configuration:nil
30                                 URL:storeURL
31                             options:nil
32                               error:nil];
33     
34     return _persistentStoreCoordinator;
35 }
36 
37 //  返回 被管理的對象模型
38 - (NSManagedObjectModel *)managedObjectModel
39 {
40     if (_managedObjectModel) {
41         return _managedObjectModel;
42     }
43     NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataNotes" withExtension:@"momd"];
44     _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
45     return _managedObjectModel;
46 }
47 
48 #pragma mark - 應用程序沙箱
49 // 返回應用程序Docment目錄的NSURL類型
50 - (NSURL *)applicationDocumentsDirectory
51 {
52     return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
53 }
複製代碼

下面是使用Core的示例代碼

複製代碼
  1 static NoteDAO *sharedManager = nil;
  2 
  3 + (NoteDAO*)sharedManager
  4 {
  5     static dispatch_once_t once;
  6     dispatch_once(&once, ^{
  7         
  8         sharedManager = [[self alloc] init];
  9         [sharedManager managedObjectContext];
 10         
 11     });
 12     return sharedManager;
 13 }
 14 
 15 
 16 //插入Note方法
 17 -(int) create:(Note*)model
 18 {
 19     
 20     NSManagedObjectContext *cxt = [self managedObjectContext];
 21     
 22     NoteManagedObject *note = [NSEntityDescription insertNewObjectForEntityForName:@"Note" inManagedObjectContext:cxt];
 23     [note setValue: model.content forKey:@"content"];
 24     [note setValue: model.date forKey:@"date"];
 25     
 26     note.date = model.date;
 27     note.content = model.content;
 28     
 29     NSError *savingError = nil;
 30     if ([self.managedObjectContext save:&savingError]){
 31         NSLog(@"插入數據成功");
 32     } else {
 33         NSLog(@"插入數據失敗");
 34         return -1;
 35     }
 36     
 37     return 0;
 38 }
 39 
 40 //刪除Note方法
 41 -(int) remove:(Note*)model
 42 {
 43     
 44     NSManagedObjectContext *cxt = [self managedObjectContext];
 45     
 46     NSEntityDescription *entityDescription = [NSEntityDescription
 47                                               entityForName:@"Note" inManagedObjectContext:cxt];
 48     
 49     NSFetchRequest *request = [[NSFetchRequest alloc] init];
 50     [request setEntity:entityDescription];
 51     
 52     NSPredicate *predicate = [NSPredicate predicateWithFormat:
 53                               @"date =  %@", model.date];
 54     [request setPredicate:predicate];
 55     
 56     NSError *error = nil;
 57     NSArray *listData = [cxt executeFetchRequest:request error:&error];
 58     if ([listData count] > 0) {
 59         NoteManagedObject *note = [listData lastObject];
 60         [self.managedObjectContext deleteObject:note];
 61         
 62         NSError *savingError = nil;
 63         if ([self.managedObjectContext save:&savingError]){
 64             NSLog(@"刪除數據成功");
 65         } else {
 66             NSLog(@"刪除數據失敗");
 67             return -1;
 68         }
 69     }
 70     
 71     return 0;
 72 }
 73 
 74 //修改Note方法
 75 -(int) modify:(Note*)model
 76 {
 77     NSManagedObjectContext *cxt = [self managedObjectContext];
 78     
 79     NSEntityDescription *entityDescription = [NSEntityDescription
 80                                               entityForName:@"Note" inManagedObjectContext:cxt];
 81     
 82     NSFetchRequest *request = [[NSFetchRequest alloc] init];
 83     [request setEntity:entityDescription];
 84     
 85     NSPredicate *predicate = [NSPredicate predicateWithFormat:
 86                               @"date =  %@", model.date];
 87     [request setPredicate:predicate];
 88     
 89     NSError *error = nil;
 90     NSArray *listData = [cxt executeFetchRequest:request error:&error];
 91     if ([listData count] > 0) {
 92         NoteManagedObject *note = [listData lastObject];
 93         note.content = model.content;
 94         
 95         NSError *savingError = nil;
 96         if ([self.managedObjectContext save:&savingError]){
 97             NSLog(@"修改數據成功");
 98         } else {
 99             NSLog(@"修改數據失敗");
100             return -1;
101         }
102     }
103     return 0;
104 }
105 
106 //查詢全部數據方法
107 -(NSMutableArray*) findAll
108 {
109     NSManagedObjectContext *cxt = [self managedObjectContext];
110     
111     NSEntityDescription *entityDescription = [NSEntityDescription
112                                               entityForName:@"Note" inManagedObjectContext:cxt];
113     
114     NSFetchRequest *request = [[NSFetchRequest alloc] init];
115     [request setEntity:entityDescription];
116     
117     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
118     [request setSortDescriptors:@[sortDescriptor]];
119     
120     NSError *error = nil;
121     NSArray *listData = [cxt executeFetchRequest:request error:&error];
122     
123     NSMutableArray *resListData = [[NSMutableArray alloc] init];
124     
125     for (NoteManagedObject *mo in listData) {
126         Note *note = [[Note alloc] init];
127         note.date = mo.date;
128         note.content = mo.content;
129         [resListData addObject:note];
130     }
131     
132     return resListData;
133 }
134 
135 //按照主鍵查詢數據方法
136 -(Note*) findById:(Note*)model
137 {
138     NSManagedObjectContext *cxt = [self managedObjectContext];
139     
140     NSEntityDescription *entityDescription = [NSEntityDescription
141                                               entityForName:@"Note" inManagedObjectContext:cxt];
142     
143     NSFetchRequest *request = [[NSFetchRequest alloc] init];
144     [request setEntity:entityDescription];
145     
146     NSPredicate *predicate = [NSPredicate predicateWithFormat:
147                               @"date =  %@",model.date];
148     [request setPredicate:predicate];
149     
150     NSError *error = nil;
151     NSArray *listData = [cxt executeFetchRequest:request error:&error];
152     
153     if ([listData count] > 0) {
154         NoteManagedObject *mo = [listData lastObject];
155         
156         Note *note = [[Note alloc] init];
157         note.date = mo.date;
158         note.content = mo.content;
159         
160         return note;
161     }
162     return nil;
163 }
複製代碼

 

CoreData高級常識

關於CoreData貌似實際開發中不多用到,基本上是個有九個公司不會使用它,由於都說是性能很差,可是做爲一個程序員,瞭解及其使用時必須了,

下面是我從一位大神那裏搬過來的一下Core詳細介紹,相信之後總有一天會幫我解決很多學習CoreData中的問題!

1、技術概覽

1. Core Data 功能初窺

  對於處理諸如對象生命週期管理、對象圖管理等平常任務,Core Data框架提供了普遍且自動化的解決方案。它有如下特性。

  (注:對象圖-Object graph的解釋:在面向對象編程中,對象之間有各類關係,例如對象直接引用另外的對象,或是經過引用鏈間接的引用其餘對象,這些關係組成了網狀的結構。 咱們把這些對象(和它們之間的聯繫)成爲對象圖。 對象圖可大可小,有繁有簡。 只包含單個字符串對象的數組就是一個簡單的表明;而包含了application對象,引用windows, menus和相關視圖對象、其餘對象這樣的結構就是複雜對象圖的例子——這是在說mainwindow.xib。

    有時,你可能想要把這樣的對象圖轉化形式,讓它們能夠被保存到文件中,以使其餘的進程或其餘的機器能夠再次將保存的內容讀出,重購對象。 這樣的過程常被成之爲「歸檔」(Archiving)。

    有些對象圖是不完整的——一般稱之爲局部對象圖(partial object graphs)。局部對象圖包含了「佔位符」(Placeholder)對象,所謂」佔位符「,就是一些暫時無內容的對象,它們將再後期被具體化。一個典 型的例子就是nib文件中包含的File's Owner對象。

  1) 對於key-value coding 和key-value observing完整且自動化的支持
除了爲屬性整合KVC和KVO的訪問方法外, Core Data還整合了適當的集合訪問方法來處理多值關係。

  2) 自動驗證屬性(property)值
Core Data中的managed object擴展了標準的KVC 驗證方法,以保證單個的數值在可接受的範圍以內,從而使組合的值有意義。(需校準翻譯)

  3) 支持跟蹤修改和撤銷操做
對於撤銷和重作的功能,除過用基本的文本編輯外,Core Data還提供內置的管理方式。

  4) 關係的維護
Core Data管理數據的變化傳播,包括維護對象間關係的一致性。

  5) 在內存中和界面上分組、過濾、組織數據

  6) 自動支持對象存儲在外部數據倉庫的功能

  7) 建立複雜請求
你不須要動手去寫複雜的SQL語句,就能夠建立複雜的數據請求。方法是在「獲取請求」(fetch request)中關聯NSPredicate(又看到這個東東了,以前用它作過正則)。NSPrdicate支持基本的功能、相關子查詢和其餘高級的 SQL特性。它還支持正確的Unicode編碼(不太懂,請高人指點), 區域感知查詢(聽說就是根據區域、語言設置調整查詢的行爲)、排序和正則表達式。

  8) 延遲操做(原文爲Futures(faulting)直譯爲期貨,這裏我的感受就是延遲操做的形象說法。請高人指教)。
Core Data 使用延遲加載(lazy loading)的方式減小內存負載。 它還支持部分實體化延遲加載,和「寫時拷貝」的數據共享機制。(寫時拷貝,說的是在複製對象的時候,實際上不生成新的空間,而是讓對象共享一塊存儲區域, 在其內容發生改變的時候再分配)。

  9) 合併的策略
Core Data 內置了版本跟蹤和樂觀鎖定(optimistic locking)來支持多用戶寫入衝突的解決。
注:樂觀鎖,假定數據通常不出現衝突,因此在數據提交更新的時候,纔對數據的衝突進行檢測,若是衝突了,就返回衝突信息。

  10) 數據遷移
就開發工做和運行時資源來講,處理數據庫架構的改變老是很複雜。Core Data的schema migration工具能夠簡化應對數據庫結構變化的任務, 並且在某些狀況下,容許你執行高效率的數據庫原地遷移工做。

  11) 可選擇針對程序Controller層的集成,來支持UI的顯示同步
Core Data在iPhone OS之上 提供NSFetchedResultsController對象來作相關工做,在Mac OS X上,咱們用Cocoa提供的綁定(Binding)機制來完成。

2. 爲什麼要使用Core Data

    使用Core Data有不少緣由,其中最簡單的一條就是:它能讓你爲Model層寫的代碼的行數減小爲原來的50%到70%。 這歸功於以前提到的Core Data的特性。更妙的是,對於上述特性你也既不用去測試,也不用花功夫去優化。

    Core Data擁有成熟的代碼,這些代碼經過單元測試來保證品質。應用Core Data的程序天天被世界上幾百萬用戶使用。經過了幾個版本的發佈,已經被高度優化。 它能利用Model層的信息和運行時的特性,而不經過程序層的代碼實現。 除了提供強大的安全支持和錯誤處理外,它還提供了最優的內存擴展性,可實現有競爭力的解決方案。不使用Core Data的話,你須要花很長時間來起草本身的方案,解決各類問題,這樣作效率不高。

    除了Core Data自己的優勢以外,使用它還有其餘的好處: 它很容易和Mac OS X系統的Tool chain集成;利用Model設計工具能夠按圖形化方式輕鬆建立數據庫的結構;你能夠用Instruments的相關模板來測試Core Data的效率並debug。 在Mac OS X的桌面程序中,Core Data還和Interface Builder集成(打開Inspector能夠看到有binding的選項,這個東東iPhone上木有。。。),按照model來建立UI變的更簡單 了。 這些功能能更進一步的幫助你縮短設計、開發、測試程序的週期。

3. Core Data不是。。。

    看了前面的介紹以後,咱們還須要瞭解一下關於Core Data常見的誤解:

  1) Core Data不是一個關係型數據庫,也不是關係型數據庫管理系統(RDBMS)。
Core Data 爲數據變動管理、對象存儲、對象讀取恢復的功能提供了支持。 它可使用SQLite做爲持久化存儲的類型。 它自己並非一個數據庫(這點很重要,好比,你可使用Core Data來記錄數據變動,管理數據,但並不能用它向文件內存儲數據)。

  2) Core Data不是銀彈
它並不能取代你寫代碼的工做。雖然能夠純粹使用XCode的數據建模工具和Interface Builder來編寫複雜程序,但在更多的程序中,你都本身動手寫代碼。

  3) Core Data並不依賴於Cocoa Bindings
Core Data + Cocoa Binding = 減小代碼數量。但Core Data徹底能夠在沒有bindings的條件下使用。例如,能夠編寫一個沒有UI,但包含Core Data的程序。

2、Core Data基礎

1. Core Data基本架構

    在大部分程序中,你要能經過某種方式打開一個包含對象歸檔的文件, 這個文件內至少要有一個根對象的引用。另外,還得能將全部的對象歸檔到文件中,若是你想要實現撤銷的功能,就還要記錄對象的更改狀況。例如,在 Employee的示例程序中,你要能打開一個包含有employee和department對象歸檔的文件,並且這個文件至少包含了一個根對象——這 裏,是一個包含全部employee的數組——請參考例圖Figure 1。 相應的,你還要能將程序中的employee、department對象歸檔到文件中去。

 Figure 1 按照Core Data文檔結構管理的對象示意圖

使用Core Data的框架,大多數的功能均可以自動實現,由於咱們有managed object context(管理對象的上下文,有時直接叫"Context")。managed object context就像是一個關卡,經過它能夠訪問框架底層的對象——這些對象的集合咱們稱之爲"persistence stack"(數據持久棧)。 managed object context做爲程序中對象和外部的數據存儲的中轉站。棧的底部是persistence object stores(持久化數據存儲),請看Figure 2的示意圖。

Figure 2 使用Core Data的文檔管理示意圖

    Core Data的使用並不限制在基於文檔的程序中(document-based application)。你也能建立一個包含Core Data 的Utility程序(請查看Core Data Utility tutorial文檔)。固然其餘類型的程序也均可以使用Core Data。

被管理對象和上下文(Managed Objects and Contexts)

    你能夠把被管理對象上下文想象成一個」聰明「的便箋簿。當你從數據持久層獲取對象時,就把這些臨時的數據拷貝拿到寫在本身的便箋簿上(固然,在便箋上對象 會 「恢復」之前的對象圖結構)。而後你就能夠爲所欲爲的修改這些值了(本子是你的,隨便畫均可以),除非你保存這些數據變化,不然持久層的東西是不會變 的。(跟修改文件後要保存是一個道理)。

    附在Core Data框架中模型對象(Model objects)常被稱爲「被管理對象」(Managed objects)。全部的被管理對象都要經過上下文進行註冊。使用上下文,你能夠在對象圖中添加、刪除對象,並記錄對象的更改(包括單個對象,或是對象間 的關係)。記錄更改後就能支持撤銷和重作的功能。同時,上下文還能保證關係更改後對象圖的完整性。

    若是你想要保存所作的修改, 上下文會保證對象的有效性。在驗證有效性後,更改會被寫入到persistent store(持久化存儲層)中。你在程序中的添加和刪除動做都會被做用在存儲的數據中。

    在你的一個程序中,可能存在多個上下文。 對於數據存儲(store)中的每一個對象,對應的都有惟一的一個被管理對象(managed object)和上下文相關聯(詳情請查看"Faulting and Uniquing"文檔)。換個角度來想,在persistent store中存儲的對象有可能被用在不一樣的上下文中,每一個上下文都有與之對應的被管理對象,被管理對象能夠被獨立的修改,這樣就可能在存儲 時致使數據的不一致。Core Data提供了許多解決這個問題的途徑(請查看"Using Managed Object"一章)。

獲取數據的請求(Fetch Requests)

    要使用上下文來獲取數據,你須要建立相應的請求(Fetch request)。 Fetch request對象包含你想獲取的對象的描述。例如:「全部 Employee」,或「全部的Employee,department是marketing,按薪資降序排列」。Fetch Request包含三個部分。使用最簡單的寫法,必須指定實體(Entity)的名稱,這就暗示了,每次智能得到一種類型的實體。 Fetch Request 還能夠包含謂詞(predicate)——注:有些地方也把這個叫斷言,我的感受謂詞更準確些。謂詞將描述對象須要知足的條件(這就和咱們在SQL里加的 限定條件差很少,正如前面的"All Employees, in the Marketing department")。另外,Fetch Request還可包含一個用於描述排序方式的對象(熟悉的Order by操做)。如圖Figure3所示:

    在程序中,你將Fetch Request這個請求發送給上下文,上下文就會從相關的數據源中查找複合條件的對象(也可能找不到),並返回。 全部的被管理對象(managed object)都必須在上下文中註冊,所以經過fetch request得到的對象自動被註冊。但如前所述,每一個在持久存儲層(persistence store)中的對象都對應一個和上下文相關的被管理對象(managed object)所以,若是在上下文中已經存在了fetch request要取的對象,那麼這個被管理對象將被返回。

    Core Data追求高執行效率。 它是「需求驅動」的,所以只會建立你確實須要的對象。對象圖不須要保留全部在數據存儲層中的對象。單純指定數據持久層的動做不會將其中全部的數據放到上下 文中去。 當你想從數據存儲層中獲取某些對象的時候,你只會獲得那些你請求的(有點羅嗦,總的意思就是須要時獲取,獲取的就是須要的)。若是你不在須要這個對象的時 候,默認狀況下它會被釋放。(固然,只是釋放這個對象,而不是從對象圖中移除該對象)。——注:我的感受有點像從新拷了一個文件的某些部分,不用了就在副 本中刪除,不會影響原件。

持久化存儲助理(Persistent Store Coordinator)

    以前提到過,程序中的對 象和外部存儲的數據經過Core Data框架中的一系列對象進行協調,這一系列的對象總的被稱爲持久存儲棧(Persistence stack)。在棧頂是被管理對象上下文(Managed object context),而棧底是持久化對象存儲層(Persistence object store)。在它們之間就是持久化存儲助理。

    事實上,持久化存儲助理定義了一個棧。從設計方面考慮,它就是能夠做爲上下 文的」外觀「, 這樣多個數據存儲(Persistence store)看起來就像是一個。 而後上下文就能夠根據這些數據存儲來建立對象圖了。持久化存儲助理智能關聯一個被管理對象的模型。若是你像要把不一樣的實體放到不一樣的存儲中去,就須要爲你 的模型實體作「分區」,方式是經過定義被管理對象模型的configurations。(請參考"Configurations"一章)。

    Figure 4演示了這樣的一個結構:employees和departments存儲在一個文件中,customers和companies存儲在另一個文件中。當你要獲取對象的時候,它們從相關的文件中自動獲取;當保存時,又被歸檔到相應的文件中。

Figure 4存儲棧—改

持久化存儲(Persistent Stores)

    持久化存儲是和單獨的一個文件或外部的數據關聯的,它負責將數據和上下文中的對象進行對應。一般,須要你直接和持久化對象存儲打交道的地方,就是指定新 的、 和程序進行關聯的外部數據的位置(例如,當用戶打開或保存一個文檔)。大多數須要訪問持久化存儲的動做都由上下文來完成。

    程序的代碼—— 特別是和被管理對象相關的部分——不該該對持久化存儲作任何假設(也就是不須要本身考慮存儲的方式或過程)。 Core Data對幾種文件格式有原生的支持。你能夠選擇一種本身程序須要的。假設在某個階段你決定換一種文件的格式,而又不想修改程序的框架,並且,你的程序作 了適當的抽象(注:這個就屬於設計方面的東東了),這時,你就能嚐到使用Core Data的甜頭了。例如,在最初的設計中,程序只從本地文件中獲取數據,而你的程序沒有去硬指定對應數據的獲取位置,而是能夠在後期指定從遠程位置添加新 的數據類型,這樣你就可使用新的類型,而不須要修改代碼。(這段仍是感受翻的不太合適)。

重要提示:

    雖然Core Dta支持SQLite做爲一種存儲類型,但它不能使用任意的SQLite數據庫。Core Data在使用的過程種本身建立這個數據庫。(詳情,請參考"Persistence Store Features")。

持久化文檔(Persistent Documents)

    你能夠經過代碼的方式建立和配置持久存儲棧,但在多數狀況下,你只是想建立一個基於文檔 的應用程序(Document-based application,這個是mac上的)來讀寫文件。這時,用NSDocument的子類NSPersistentDocument可讓你感覺到使 用Core Data的便利。默認情況下,NSPersistentDocument就已經建立了它本身的持久存儲棧,其中包含了上下文,和單個的持久對象存儲,來處 理這樣文檔和外部數據「一對一」的映射關係。

    NSPersistentDocument類提供了訪問文檔的上下文的方法,也實現了標準的NSDocument方法來經過Core Data讀寫文件。 通常說來,你不須要編寫額外的代碼來處理對象的持久化。

    持久化文檔的撤銷(undo)操做也被集成在被管理對象的上下文中。

    被管理對象和被管理對象模型(Managed Objects and the Managed Object Model)
爲 了管理對象圖,也爲了提供對象持久化的功能,Core Data須要對對象有很強的描述能力。被管理對象模型就是程序中對象、實體描述的概要圖,如圖Figure 5所示。建立模型的經常使用作法是經過Xcode的圖形化建模工具Date Model Design tool。可是若是你願意的話,也能夠在運行時經過代碼來建模。

Figure 5 有兩個實體的對象模型

模型由多個實體描述對象構成,每一個描述提供實體的某項元數據,它們包含實體名、實體在程序中的類名(固然,類名和實體名不須要一致)、屬性還有關係。屬性和關係依次被屬性和關係描述對象所表明,如圖Figure 6所示。

Figure 6 帶有兩個屬性和一個關係的的實體描述

 


被管理對象必須是NSManagedObject或其子類的實例。 NSManagedObject可用來表示任何實體。它使用內部私有的存儲機制來維護自身的屬性,並執行一個被管理對象所必須的基本操做。一個被管理對象 擁有一份實體描述的引用。在使用時,它經過實體描述來找到自身的元數據,包括實體名和屬性、關係的信息。你也能夠繼承NSManagedObject來執 行額外的操做。

被管理對象模型(Managed Object Models)

    多數Core Data的功能依賴於你建立的,用來描述程序的實體及其屬性、關係的模型圖。 模型圖由NSManagedObjectModel所表示。通常說來,模型的信息越充實,Core Data能提供的功能就越好。 下文講解了對象模型的特性,以及如何在程序中建立、使用對象模型。

被管理對象模型的特性

    被管理對象模型是 NSManagedObjectModel的實例。它描述了你在程序中使用的實體的概要信息。(若是讀者不瞭解entity、property、 attribute和relationship的含義,請先查看"Core Data Basics"和"Cocoa Design Patterns"文檔中的"Object Modeling"一節)

實體(Entities)

    模型包含了NSEntityDescription對象,NSEntityDescription對象指代了模型的實體。關於實體由兩個重要特徵:名稱 (name)和類名(name of class)。你應該弄清楚實體、實體的類和做爲實體實例的被管理對象之間的區別。

    NSEntityDescription 對象可包含NSAttributeDescription對象(指代實體的attribute)和NSRelationshipDescription對 象(指代實體間的relationship)。實體也可能包含fetched屬性,該屬性由NSFetchedPropertyDescription指 代,模型中有對應的fetch請求的模板,fetch請求由NSFetchRequest所指代。

實體的繼承關係

    實體的繼承和類 的繼承很相似,固然,也一樣有用。 若是你有若干個類似的實體,就能夠抽離出它們的共有特性做爲一個「父實體」,就省去了在多個實體中都指定相同的屬性。 例如,你能夠定義一個包含firstName和lastName的「Person」實體,而後在定義子實體"Employee"和"Customer"。

    若是是使用Xcode的可視化建模工具來建立模型,你就能夠經過以下圖的方式爲一個實體指定父級實體。

 Figure1 Xcode中爲一個實體指定父實體



若是你想在代碼中建立繼承關係。就須要自頂向下來執行。不能直接指定實體的父實體,而只能給一個實體指定子實體(使 用setSubentities:)。這 就是說,若是你想給A實體指定父實體,就只能把A做爲數組中的一個元素,調用目標父實體setSubentities:的方式來設置。

抽象實體

    你能夠把一個實體指定爲「抽象實體」,也就是說,你不打算使用這個實體來建立實例。一般,當你想把這個實體做爲父實體,而有子實體來實現詳細內容的時候, 就 把它聲明「抽象實體」。(和抽象類很像)。例如,在一個繪圖程序中,你可能會設計一個Graphic實體,它包含了x和y座標信息、顏色、繪製區域,而你 不會去建立一個Graphic的實例,而是使用具體的子實體——Circle、TextArea、Line。(這些基本的東西就不給大牛們再羅嗦 了。。。)

Properties(屬性,這個和Attributes的意思同樣,實在區別不出來,只好上英語了)

    實體的 Properties是它的attributes和relationship,包含了fetched屬性(若是有的話)。每一個property都有名稱和 類型。 Attribute也可能有默認值。property的名稱不能和NSObject和NSManagedObject類中的無參方法名相同。例如,不能把 property命名爲"description"。

    臨時屬性(Transient Property)也是做爲模型的一部分,可是不做爲實體實例的數據保存在持久存儲層。 Core Data也會跟蹤臨時屬性的變化,以備撤銷操做時使用。

    注意:若是你用模型外的信息對臨時屬性執行撤銷操做,Core Data將不會使用舊值,調用你的set方法——它只會更新快照信息(snapshot information)。(這段怪怪的,用到的話在修改一下翻譯吧)

Attributes

    Core Data內部支持各類attribute的類型,例如string,date,integer(NSString, NSDate, NSNumber)。若是你使用那些不支持的數據,你須要用到在「Non-Standard Persistent Attributes」介紹到的技術。

    你能夠將一個attribute聲明爲「可選」(optional),可選的attribute不 必須有值,可是,不鼓勵你將屬性置空——尤爲是數字值(更好的解決方案是使用強制的值,在這裏,咱們用默認值,例如0)。 這樣作的緣由是爲了配合SQL中對於空值NULL作比較的操做:NULL不一樣於Objective-C中的nil。 數據庫中的NULL不一樣於0,搜索0值的操做不會匹配到值爲NULL的列。

false == (NULL == 0)
false == (NULL != 0)

    並且,在數據庫中,NULL也不等於空字符串或是空的數據對象:

false == (NULL == @"")
false == (NULL != @"")

    它們之間一點關係都沒有。

關係(Relationships)

    Core Data支持對1、對多的關係,也支持fetched屬性。 Fetched property表示了一種「弱」的、單項的關係。 在employees和departments的例子中, department 的一個fetched property多是「最近僱傭人」(recent hires),而反過來,employee不會擁有這樣的關係。

獲取數據請求的模板(Fetch Request Templates)

    咱們使用NSFetchRequest類來描述數據請求,利用數據請求從持久存儲(persistent store)中獲取對象。 常常須要屢次執行一樣的請求,或是執行某種模式的請求,可是其中包含可變的元素(如查找條件)——這些元素常常有用戶提供。 例如,在運行的時候,你要根據用戶須要獲取某個做者在某個指定日期後的出版的全部出版物。

    你能夠預約義請求,把它們做爲模板存儲在被管理對象模型中。 預約義的模板在你須要的時候就能夠取出使用。一般狀況下,咱們經過Xcode的data modeling tool工具建立請求模板。模板能夠包含變量,如圖Figure 2所示。

Figure 2  Xcode predicate builder

 
關於Fetch request templates的詳細信息,請查看"Accessing and Using a Managed Object Model at Runtime"的描述。

用戶信息字典(User Info Dictionaries)

    模型中的許多元素,諸如entities, attributes, relationships,都有相關的用戶信息字典。用熟悉的鍵-值對,你能夠向其中放置任何你須要的數據。這裏經常使用的信息有實體的版本詳情,還有針對 fetched property,給謂詞(predicate)用的值。

配置(Configurations)

    配置包含了一個名稱和若干個相關的實體。實體的集合是能夠重疊的——這就是說,一個實體能夠出如今多個配置中。在代碼中,咱們使用 setEntities: forConfiguration:的方法來指定配置。也能夠用Xcode的建模工具來指定(選中某個實體,就在屬性窗口的第三個,就是一個小扳手的符 號)。要獲取某項配置的實體,須要用entitiesForConfiguration:的方法。

    通常說來,若是你想把不一樣的實體存放在不一樣的存儲中去,就可能用到配置。一個持久化存儲助理(persistent store coordinator)只能有一個被管理對象模型。因此,默認狀況下,和助理關聯的某個存儲必須包含一樣的實體。要想繞過這個限制,你能夠建立一個包含 實體子集的模型,而後爲每個子集建立配置,這樣一來,使用這個模型建立助理,當你須要添加存儲時,可以使用不一樣的配置指定對應的存儲屬性。當你建立配置的 時候,須要記住,不能建立跨存儲的關係。

使用被管理對象模型

    一般可使用Xcode的建模工具來建立模型(請參考"Create a managed object with Xcode")。你也能夠所有使用代碼來建立(請參考"Core Data Utility Tutorial")。

編譯數據模型

    數據模型是一種部署資源。 在模型中,除了有實體和屬性的詳細信息外,用Xcode建立的模型還包含了一些額外的視圖信息,包括佈局、顏色等等。這些信息在運行時不是必須的。模型文 件在編譯的過程當中會刪除這些額外信息以保證儘量高效的加載。xcdatamodel「源」文件會被momc編譯器編譯爲mom的目標文件。

    "mom" 位於 /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/,若是你想把它用在本身的 build腳本中,格式是:mom source destination, source 就是Core Data Model文件,destination就是輸出的mom文件。

加載數據模型

    在一些狀況下,你不須要寫任何加載模型的代碼。若是你使用基於文檔的程序框架(Document-based application),NSPersistentDocument會管理諸如查找模型、加載模型的任務。 若是你建立了非Document-based application,並且裏面又用到了Core Data,通常將獲取模型的代碼放在application delegate裏。模型的存儲名稱——也就是文件名,

    和運行時的名稱是不相關的,一旦模型被加載,文件名就沒有什麼意義了。也就是說,對模型文件,你能夠隨意命名。

    若是你想手動加載模型,有兩種方式可用,它們各有各的好處:

    你能夠從指定的bundle集合裏建立整合模型,使用以下的類方法:
mergeModelFromBundles:

    也能夠用指定的URL加載單個的模型,使用以下的實例方法:
initWithContentsOfURL: (這個方法相信你們都用過)

    若不須要考慮分開加載模型,第一個類方法很適用。例如:在你的程序中和程序連接的framework裏都有你想要加載的模型。這個類方法可讓你很輕鬆的加載全部的模型,而不須要考慮模型文件的名稱,也不用特定的初始化方法來保證全部的模型都被找到。

    可是當你有多個模型要加載,特別是這些模型都表明了一個schema的不一樣版本,這時,知道要加載哪一個模型就很重要了(合併包含相同實體的模型可能致使命 名衝突和錯誤,咱們以前「一鍋端」的方法不太合適了)。在這種狀況下,咱們能夠用第二個實例方法。 另外,有時咱們也須要將模型存儲在bundle以外,也須要用這個方法從指定的URL位置加載模型。

    還有一點須要說明:咱們還有一個類方法 modelByMergingModels:能夠用。像mergedModelFromBundles:方法同樣,它也能合併給定的若干個模型。這樣,我 們就能夠經過URL來逐一加載模型,而後在建立助理對象以前將它們整合爲一個。

改變模型

    因爲模型描述了存儲層數據的結構,任何改變模型的動做都將使其不在適配於以前建立的存儲層。 若是你改變了模型的結構,就須要將當前存儲層的數據遷移到新版本。(請參考"Core Data Model Versioning and Data Migration Programming Guide"文檔)。例如:若是你添加了新的實體,或新的屬性,你將沒法打開舊的存儲;若是你添加了驗證的限制,或者爲屬性添加了新的缺省值,你就能夠打 開舊的存儲。

在運行時訪問和適用被管理對象模型

    在運行時,被管理對象模型就是一個簡單的「對象圖」(這個概念以前提到過),認識到這點很重要,尤爲是當你須要用代碼來訪問模型的詳細信息時。例如:修改 模型(你只能在runtime以前這樣作,請參考 NSManagedObjectModel),取回信息(如本地化實體名,屬性數據類型,或數據請求模板)。

    在運行時訪問模型有不少方法,經過持久棧最終從持久化存儲助理獲得模型,代碼以下:
[[aManagedObjectContext persistentStoreCoordinator]managedObjectModel];

    你也能夠經過實體描述獲得模型,所以給定一個被管理對象,你就能夠獲得它的實體描述,進而得到模型。代碼以下:
[[aManagedObject entity] managedObjectModel];

    某些狀況下,你要維護模型的「直接」引用,也就是說,一個直接返回模型的方法。NSPersistentDocument提供了 managedObjectModel方法,能夠返回一個模型,該模型和在文檔的上下文中使用的持久化存儲助理相關聯。若是你使用Core Data Appplication的模板,application delegate將負責模型的引用。

經過代碼建立獲取數據請求模板(Fetch Request Templates)

    你能夠經過代碼建立數據請求模板並將其和模型關聯,方法是:setFetchRequestTemplate: forName:如Listing-1所示。 提醒一下:你只能在模型被助理(coordinator)使用以前修改它。

    Listing 1 經過代碼建立獲取數據請求模板

NSManagedObjectModel *model = …;
NSFetchRequest * requestTemplate = [[NSFetchRequest alloc]init];
NSEntityDescription *publicationEntity =
    [[model entitiesByName] objectForKey: @"Publication"];
[requestTemplate setEntity: publicationEntity];

NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
    @"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
        (mainAuthor.lastName like[cd] $LAST_NAME) AND \
        (publicationDate > $DATE)"];
[requestTemplate setPredicate: predicateTemplate];

[model setFetchRequestTemplate: requestTemplate
    forName: @"PublicationForAuthorSinceDate"];
[requestTemplate release];

訪問請求模板

    你能夠用"Accessing and Using a Managed Object Model at Runtime"裏介紹的代碼片斷來獲取並使用請求模板。替換字典必須包含和模板中定義的變量對應的鍵。若是你想測試null值,必須使用NSNull對 象——參考"Using Predicates"。(注:這裏的替換字典很好理解,以前的模板中用到了諸如$FIRST_NAME, $LAST_NAME, $DATE這些東西,就至關於咱們在模板中建立好的「變量」,咱們須要把一個模板「具體化」,就用替換字典,將裏面的變量對應一個值,這裏看代碼就明白 了。)

NSManagedObjectModel *model = …;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    @"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
    [NSDate dateWithTimeIntervalSinceNow: -31356000], @"DATE", nil]; //這裏的FIRST_NAME, LAST_NAME, DATE和咱們以前模板裏的$FIRST_NAME, $LAST_NAME和$DATE對應
NSFetchRequest *fetchRequest =
    [model fetchRequestFromTemplateWithName: @"PublicationForAuthorSinceDate"
            substitutionVariables: substitutionDictionary]; //從以前的model中拿出請求模板,而後設定替換字典
NSArray *results =
    [aManagedObjectContext executeFetchRequest: fetchRequest error: &error];

    要是模板裏不包含可替換的變量,你要麼

1. 使用fetchRequestFromTemplateWithName: substitutionVariables: 方法,傳遞nil給第二個參數
或者:
2. 使用fetchRequestTemplateForName: 並將結果copy。這個方法不須要傳遞「替換變量」這個參數,可是若是你要用返回值自己,將會有異常拋出(沒法在不可變的模型中修改命名的數據請 求"Can't modify named fetch request in an immutable model")。

本地化被管理對象模型

    你能夠對模型的大部份內容作本地化處理,包括實體和屬性名,還有錯誤信息。要明白,「轉成你本身的語言」也是本地化的一部分。 即便你不打算提供外語版本, 顯示「天然語言」的出錯提示信息也會有更好的用戶體驗。例如:「First Name is a required property」就比"firstName is a required property"更好。(後面的這個更像是開發者用的log,顯示的是變量名,這裏不太明顯)。

    要想對模型進行本地化處理,須要提供一個本地化字典,模式以下:

    Table 1 針對被管理對象模型的本地化字典鍵值對應關係:

  • Key                                                                                                                   Value                                               Note
  • "Entity/NonLocalizedEntityName"                                                     "LocalizedEntityName"           
  • "Property/NonLocalizedPropertyName/Entity/EntityName"         "LocalizedPropertyName"                       1
  • "Property/NonLocalizedPropertyName"                                          "LocalizedPropertyName"
  • "ErrorString/NonLocalizedErrorString"                                             "LocalizedErrorString"

    備註:(1)在不一樣實體中的屬性,擁有相同的原始名稱,但須要不一樣的本地化名稱,適用於該格式。

    咱們能夠經過localizationDictionary方法來訪問本地化字典。注意:在Mac OS X 10.4上,這個方法可能返回nil,除了Core Data爲了某些特定目的(如報告本地化的錯誤描述)延遲加載本地化字典。

字符串文件

    處理模型的本地化最簡單的方法就是建立對應的字符串文件——字符串文件名和模型文件名一直,可是後綴名用.strings。(例如,模型文件名爲 MyDocument.xcdatamodel,對應的字符串文件名就爲MyDocumentModel.strings;若是模型文件已經包含了 Model後綴,你必須再附加一個Model,因此,若是模型文件名爲JimsModel.xcdatamodel對應的字符串文件名爲 JimsModelModel.strings)。字符串文件格式和標準字符串文件相似(請參考"Localizing String Resources"),可是對應的鍵值要遵循Table-1中的規則。

    一個模型的字符串文件實例:

  1. "Entity/Emp" = "Employee";
  2. "Property/firstName" = "First Name";
  3. "Property/lastName" = "Last Name";
  4. "Property/salary" = "Salary";

   更詳細的示例請參考"NSPersistentDocument Core Data Tutorial"。

代碼實現設置本地化字典

    你能夠在運行時設定本地化字典,適用NSManagedObjectModel的setLocalizationDictionary:方法便可。你必須 建立一個符合Table-1格式的字典,並把它和模型關聯。必須保證在模型被使用(獲取或建立被管理對象)以前作這些工做,由於再使用後模型就不可編輯 了。 Listing 3演示了建立包含本地化字典的被管理對象模型。實體名稱叫「Run」,它有兩個屬性: "date"和"processID",分別是date和integer類型。process ID的值不能爲負。

Listing 3 經過代碼建立被管理對象模型

  • NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
  • NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
  • [runEntity setName:@"Run"];
  • [runEntity setManagedObjectClassName:@"Run"];
  • [mom setEntities:[NSArray arrayWithObject:runEntity]];
  • [runEntity release];
  • NSMutableArray *runProperties = [NSMutableArray array];
  • NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
  • [runProperties addObject:dateAttribute];
  • [dateAttribute release];
  • [dateAttribute setName:@"date"];
  • [dateAttribute setAttributeType:NSDateAttributeType];
  • [dateAttribute setOptional:NO];
  • NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
  • [runProperties addObject:idAttribute];
  • [idAttribute release];
  • [idAttribute setName:@"processID"];
  • [idAttribute setAttributeType:NSInteger32AttributeType];
  • [idAttribute setOptional:NO];
  • [idAttribute setDefaultValue:[NSNumber numberWithInt:0]];
  • NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
  • NSString *validationWarning = @"Process ID < 0";
  • [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
  •     withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
  • [runEntity setProperties:runProperties];
  • NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
  • [localizationDictionary setObject:@"Process ID"
  •     forKey:@"Property/processID/Entity/Run"];
  • [localizationDictionary setObject:@"Date"
  •     forKey:@"Property/date/Entity/Run"];
  • [localizationDictionary setObject:@"Process ID must not be less than 0"
  •     forKey:@"ErrorString/Process ID < 0"];
  • [mom setLocalizationDictionary:localizationDictionary];

    這段代碼寫的比較多,這裏再也不解釋了。本地化字典的代碼在最後。建立一個符合格式的localizationDictionary,而後用model調用便可。

-----------------------------------------------------------------------------------

iOS -表視圖控制器與CoreData

 

  在接觸到CoreData時,感受就是蘋果封裝的一個ORM。CoreData負責在Model的實體和sqllite創建關聯,數據模型的實體類就至關於Java中的JavaBean, 而CoreData的功能和JavaEE中的Hibernate的功能相似,最基本是二者都有經過對實體的操做來實現對數據庫的CURD操做。CoreData中的上下文(managedObjectContext)就至關於Hibernate中的session對象, CoreData中的save操做就和Hibernate中的commit,還有一些類似之處,在這就不一一列舉了。(上面是筆者本身爲了更好的理解CoreData而作的簡單類比,若是學過PHP的ThinkPHP框架的小夥伴們也能夠和TP中的ORM類比)。

  那麼TableView爲何會愛上CoreData呢?下面會通個代碼給出他們相愛的緣由。就舉一個IOS開發中的經典的demo:通信錄來講明問題。

    1.在TableView沒遇到CoreData的時候咱們怎麼經過動態表視圖來顯示咱們的通信錄的內容呢?也就是說咱們通信錄的數據結構該如何組織呢?

    爲了在TableView中顯示咱們的信息咱們這樣設計咱們的數據結構:

      1.整個TableView是一個可變的數組tableArray;

      2.tableArray中的每一個元素又是一個存放分組的字典sectionDictionary;

      3.在sectionDictionary中咱們存放着兩個鍵值對 header和items, header中存放的時section中的名字,items中存放的時每一個section中的用戶信息

      4.items中又是一個數組rowsArray, rowsArray中存放的又是一個字典userInfoDictionary, 在userInfoDictionary中存放着咱們要顯示的信息

    千字不如一圖,看到上面對咱們要設計的數據結構的描述會有點迷糊,下面來張圖吧:

    2.數據結構咱們設計好了,那麼如何用代碼生成咱們的測試數據(數據的組織形式如上圖所示),下面的代碼就是生成咱們要在tableView中顯示的數據,生成的數組存儲在tableArray中,代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
  *手動建立咱們在動態表視圖上顯示的數據格式
  *整個數據存儲在一個數組中
  *數組中每個元素是一個自動,字典的key是sectionHeader的值,value是該section中以數組形式存的數據
  *section中的每一行對應着一個數組元素,數組元素中又存儲着一個字典,字典中存儲着用戶的具體數據。
  */
 
//爲咱們的數組分配存儲空間, 表明着有20個section
self.telBook = [NSMutableArray arrayWithCapacity:26];
 
//爲咱們的section設置不一樣的header
char  header =  'A' ;
 
//計數
static  int  number = 0;
for  ( int  i = 0; i < 26; i ++) {
     //新建字典來存儲咱們每一個section中的數據, 假設每一個section中有1個數組
     NSMutableDictionary *sectionDic = [NSMutableDictionary dictionaryWithCapacity:1];
     
     //建立字典中的數組,數組中以鍵值對的形式來儲存用戶的信息
     NSMutableArray *rowArray = [NSMutableArray arrayWithCapacity:3];
     for  ( int  j = 0; j < 3; j ++)
     {
         //建立存儲用戶信息的字典
         NSMutableDictionary *user = [NSMutableDictionary dictionaryWithCapacity:2];
         
         //生成測試數據
         NSString *name = [NSString stringWithFormat:@ "User%03d" , number];
         NSString *tel = [NSString stringWithFormat:@ "12345%03d" , number++];
         
         //加入字典中
         [user setObject:name forKey:@ "name" ];
         [user setObject:tel forKey:@ "tel" ];
         
         //把字典加入數組
         [rowArray addObject:user];
     }
     
     //把rowArray添加到section字典中
     NSString *key = [NSString stringWithFormat:@ "%c" ,(header+i)];
     [sectionDic setObject:key forKey:@ "header" ];
     [sectionDic setObject:rowArray forKey:@ "items" ];
     
     //把section添加到總的數組中
     [self.telBook addObject:sectionDic];
}

 

    3.把咱們用代碼建立的模擬數據在咱們的TableView中進行顯示,在相應的函數中根據咱們生成的數據返回相應的值顯示在TableView中,顯示代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#pragma mark - Table view data source
//返回Section的個數,即咱們telBook數組元素的個數
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
     return  self.telBook.count;
}
 
  //返回每一個section中的行數,即section中的數組元素的個數
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
     NSArray *rowArray = self.telBook[section][@ "items" ];
     return  rowArray.count;
}
 
//給每一個分組設置header
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
     //獲取每一個section中的header
     NSString *title = self.telBook[section][@ "header" ];
     return  title;
}
 
 
//獲取cell並添加完數據發揮
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@ "Cell"  forIndexPath:indexPath];
     
     //獲取secion中的數據數組
     NSArray *items = self.telBook[indexPath.section][@ "items" ];
     
     //獲取數組中的每一項的一個字典
     NSString *name = items[indexPath.row][@ "name" ];
     NSString *tel = items[indexPath.row][@ "tel" ];
     
     //給sel設置值
     cell.textLabel.text = name;
     cell.detailTextLabel.text = tel;
     
     return  cell;
}

    4.上面給出的時關鍵代碼,至於怎麼配置TableView的Cell模板或者如何把TableViewController和Storyboard中的ViewController綁定,在前面的博客中都有介紹,在這小編就不作贅述。運行結果和上面的圖片是同樣的。

  

  上面的東西只是這篇博文的引子,爲了顯示上面的數據結構咱們這樣作是否是太麻煩了,並且上面的數據是不能被持久化存儲的。若是給咱們的數據都要轉換成上面的數據組織形式,想必因爲所給數據結構的不肯定,因此轉換起來是至關的複雜的。TableView之因此會愛上CoreData,是由於咱們的CoreData會簡化咱們對數據的操做,而且會持久化到sqlite中。CoreData至關於TableView和sqllite的紐帶,說的專業一些就是映射,那麼咱們CoreData如何使用纔會簡化咱們的操做呢?下面將要介紹的纔是這篇博客中的重點:咱們如何使用CoreData纔會讓TableView愛上它呢?

  1.新建一個Empty Application, 在新建工程的時候,不要忘了把Use Core Data給選中,選中Use Core Data會自動引入Core Data框架庫和在AppDelegate.h和AppDelegate.m中進行相應的配置,而且同時還自動生成一個以本應用名命名的Data Model文件,咱們能夠在Data Model文件中添加咱們的數據模型, 添加好的數據模型咱們會在生成數據實體類時使用(和JavaBean相似)

    (1)AppDelegata.m中多出的部分代碼以下,從多出的部分代碼就能夠看出,CoreData會把咱們的數據實體和sqllite創建起一一對應的關係:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
     if  (_managedObjectModel != nil) {
         return  _managedObjectModel;
     }
     NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@ "Demo083101"  withExtension:@ "momd" ];
     _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
     return  _managedObjectModel;
}
 
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
     if  (_persistentStoreCoordinator != nil) {
         return  _persistentStoreCoordinator;
     }
     
     NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@ "Demo083101.sqlite" ];
     
     NSError *error = nil;
     _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
     if  (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
         NSLog(@ "Unresolved error %@, %@" , error, [error userInfo]);
         abort ();
     }   
     
     return  _persistentStoreCoordinator;
}

 

    (2)咱們能夠經過 projectName.xcdatamodeld中建立咱們的數據實體模型,以下圖所示

 

    (3)經過建立好的數據實體模型來建立咱們的實體類(和JavaBean相似的東西)建立過程以下圖,點擊下一步之後,選中建立的實體模型便可:

 

  2.CoreData準備的差很少啦,該咱們的TableView出場啦,在Empty Application中默認的時沒有storyboard, 若是你又想經過storyboard來簡化你的操做,得給應用建立一個storybaord纔對,建立過程以下:

    (1)第一步建立一個storyboard文件,命名爲Main,以下圖所示

 

    (2)第二步:設置從storyboard來啓動, 在Main InterFace中選中咱們建立的storyboard便可

 

    (3) 第三步修改AppDelegate.m中的函數以下所示,把初始化的工做交給咱們建立的storyboard進行:

1
2
3
4
- ( BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
     return  YES;
}

 

  3.配置工做完成接下來就是TableView和CoreData相愛的過程啦,如何在storyboard中對TableView的cell進行配置在這兒就不贅述了,下面給出咱們要經過TableView和CoreData來實現什麼功能。

    (1)咱們要實現對通信錄的增刪改查,主要需求入下圖所示:

  

  (2)實現添加功能,點擊右上角的添加按鈕時會跳轉到添加頁面,在添加頁面中有兩個TextField來接受用戶的輸入,點擊添加按鈕進行數據添加。AddViewController.m中的主要代碼以下。

    a.須要用到的屬性以下, 用NSManagedObejectContext的對象來操做CoreData中的數據,和Hibernate中的session的對象類似

1
2
3
4
5
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@property (strong, nonatomic) IBOutlet UITextField *numberTextField;
 
//聲明CoreData的上下文
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;

 

    b.獲取UIApplication的單例application, 而後再經過application獲取delegate, 最後經過delegate來獲取上下文,代碼以下:

1
2
3
4
//經過application對象的代理對象獲取上下文
UIApplication *application = [UIApplication sharedApplication];
id delegate = application.delegate;
self.managedObjectContext = [delegate managedObjectContext];

 

​    c.編輯點擊button要回調的方法,在點擊添加按鈕時首先得經過上下文獲取咱們的實體對象,獲取完實體對象後再給實體對象的屬性賦上相應的值,最後調用上下文的save方法來存儲一下咱們的實體對象。添加完之後還要經過navigationController來返回到上一層視圖,代碼以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (IBAction)tapAdd:(id)sender {
     
     //獲取Person的實體對象
     Person *person = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Person  class ]) inManagedObjectContext:self.managedObjectContext];
     
     //給person賦值
     person.name = self.nameTextField.text;
     person.number = self.numberTextField.text;
     person.firstN = [NSString stringWithFormat:@ "%c" , pinyinFirstLetter([person.name characterAtIndex:0])-32];
     
     //經過上下文存儲實體對象
     NSError *error;
     if  (![self.managedObjectContext save:&error]) {
         NSLog(@ "%@" , [error localizedDescription]);
     }
 
     //返回上一層的view
     [self.navigationController popToRootViewControllerAnimated:YES];
     
}

 

​  (3)實現上面的代碼只是經過CoreData往sqlite中添加數據,要想在咱們的TableView中顯示還須要經過CoreData把咱們的存儲在sqlite中的數據來查詢出來,再用CoreData給咱們提供的方法把查詢結果作一個轉換,轉換成適合TableView顯示的數據,下面給出相應的獲取數據的代碼。

    a.在TableViewController咱們須要聲明以下兩個屬性,一個用於獲取上下文,一個用於存儲返回結果

1
2
3
4
//聲明經過CoreData讀取數據要用到的變量
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
//用來存儲查詢並適合TableView來顯示的數據
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

                                          

    b.​在viewDidLoad中獲取上下文

1
2
3
4
//經過application對象的代理對象獲取上下文
UIApplication *application = [UIApplication sharedApplication];
id delegate = application.delegate;
self.managedObjectContext = [delegate managedObjectContext];

 

    c.在viewDidLoad中經過上下文來查詢數據,並存儲在fetchedResultsController中, 在獲取數據的過程當中咱們須要定義UIFetchRequest 和排序規則,代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*********
  經過CoreData獲取sqlite中的數據
  *********/
 
//經過實體名獲取請求
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Person  class ])];
 
//定義分組和排序規則
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@ "firstN"  ascending:YES];
 
//把排序和分組規則添加到請求中
[request setSortDescriptors:@[sortDescriptor]];
 
//把請求的結果轉換成適合tableView顯示的數據
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:@ "firstN"  cacheName:nil];
 
//執行fetchedResultsController
NSError *error;
if  ([self.fetchedResultsController performFetch:&error]) {
     NSLog(@ "%@" , [error localizedDescription]);
}

 

    d.把查詢到的數據顯示在TableView中代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#pragma mark - Table view data source
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
     //咱們的數據中有多少個section, fetchedResultsController中的sections方法能夠以數組的形式返回全部的section
     //sections數組中存的是每一個section的數據信息
     NSArray *sections = [self.fetchedResultsController sections];
     return  sections.count;
}
 
//經過獲取section中的信息來獲取header和每一個secion中有多少數據
 
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
     NSArray *sections = [self.fetchedResultsController  sections];
     //獲取對應section的sectionInfo
     id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
     
     //返回header
     return  [sectionInfo name];
}
 
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
 
     NSArray *sections = [self.fetchedResultsController sections];
     id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
     
     //返回每一個section中的元素個數
     return  [sectionInfo numberOfObjects];
}
 
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@ "Cell"  forIndexPath:indexPath];
     
     //獲取實體對象
     Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
     
     cell.textLabel.text = person.name;
     cell.detailTextLabel.text = person.number;
     
     // Configure the cell...
     
     return  cell;
}

 

 

  (4) 經上面的代碼,咱們就能夠經過CoreData查詢sqlite, 而後把查詢測數據結果顯示到TableView中,但是上面的代碼有個問題,就是當經過CoreData來修改或着添加數據時,TableView上的內容是不跟着CoreData的變化而變化的,接下來要作的就是要綁定TableView和CoreData的關係。即經過CoreData修改數據的同時TableView也會跟着改變。

    a.要想實現TableView和CoreData的同步,咱們須要讓TableView對應的Controller實現協議NSFetchedResultsControllerDelegate, 而後再ViewDidLoad中進行註冊,在添加上相應的回調代碼便可。實現協議的代碼以下:

1
2
3
4
5
#import <UIKit/UIKit.h>
 
@interface MyTableViewController : UITableViewController<NSFetchedResultsControllerDelegate>
 
@end


    b.進行委託回調的註冊,在viewDidLoad中添加

1
2
//註冊回調,使同步生效
self.fetchedResultsController.delegate = self;

    c.添加相應的委託回調的方法,咱們能夠到Help中的API中去複製, 查詢NSFetchedResultsControllerDelegate,找到相應的回調代碼複製過來而後再作簡單的修改便可, 實現回調的方法代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*
  Assume self has a property 'tableView' -- as is the case for an instance of a UITableViewController
  subclass -- and a method configureCell:atIndexPath: which updates the contents of a given cell
  with information from a managed object at the given index path in the fetched results controller.
  */
 
//當CoreData的數據正在發生改變是,FRC產生的回調
- ( void )controllerWillChangeContent:(NSFetchedResultsController *)controller {
     [self.tableView beginUpdates];
}
 
//分區改變情況
- ( void )controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
            atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
     
     switch (type) {
         case  NSFetchedResultsChangeInsert:
             [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                           withRowAnimation:UITableViewRowAnimationFade];
             break ;
             
         case  NSFetchedResultsChangeDelete:
             [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                           withRowAnimation:UITableViewRowAnimationFade];
             break ;
     }
}
 
//數據改變情況
- ( void )controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
        atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
       newIndexPath:(NSIndexPath *)newIndexPath {
     
     UITableView *tableView = self.tableView;
     
     switch (type) {
             
         case  NSFetchedResultsChangeInsert:
             //讓tableView在newIndexPath位置插入一個cell
             [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
             break ;
             
         case  NSFetchedResultsChangeDelete:
             [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
             break ;
             
         case  NSFetchedResultsChangeUpdate:
             //讓tableView刷新indexPath位置上的cell
             [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
             break ;
             
         case  NSFetchedResultsChangeMove:
             [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
             [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
             break ;
     }
}
 
//當CoreData的數據完成改變是,FRC產生的回調
- ( void )controllerDidChangeContent:(NSFetchedResultsController *)controller {
     [self.tableView endUpdates];
}

 

  (5)通過上面的代碼就能夠實現CoreData和TableView的同步啦,到此會感受到TableView結合着CoreData是如此的順手,雖然配置起來較爲麻煩,但仍是比較中規中矩的,只要循序漸進的來,是不難實現的。所以TableView深愛着CoreData. 上面咱們完成了經過CoreData來對數據的插入和查詢並同步到TableView中,下面將會介紹到如何對咱們的Cell進行刪除。

    a.想經過TableView來刪除數據的話得開啓咱們的TableView的編輯功能

1
2
3
4
5
6
7
//開啓編輯
// Override to support conditional editing of the table view.
- ( BOOL )tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
     // Return NO if you do not want the specified item to be editable.
     return  YES;
}

 

​    b.開啓編輯功能之後咱們就能夠在tableView的對應的方法中來實現刪除功能啦,當點擊刪除時,咱們需呀獲取cell對應的索引在CoreData中的實體對象,而後經過上下文進行刪除,在save一下便可。由於CoreData和TableView已經進行了同步,因此刪除後TableView會自動更新,刪除代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Override to support editing the table view.
- ( void )tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
     if  (editingStyle == UITableViewCellEditingStyleDelete)
     {
         //經過coreData刪除對象
         //經過indexPath獲取咱們要刪除的實體
         Person * person = [self.fetchedResultsController objectAtIndexPath:indexPath];
         
         //經過上下文移除實體
         [self.managedObjectContext  deleteObject:person];
         
         //保存
         NSError *error;
         if  ([self.managedObjectContext save:&error]) {
             NSLog(@ "%@" , [error localizedDescription]);
         }
     }
}

​    c.默認的刪除按鈕上顯示的是Delete, 能夠經過下面的方法進行修改,代碼以下:

1
2
3
4
5
6
//設置刪除的名字
 
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
     return  @ "刪除" ;
}

   (6)到這一步刪除功能算是完成了,還有最後一個功能點,就是更新咱們的數據。更新數據經過點擊相應的cell,把cell上的數據傳到UpdateView的頁面上,而後進行更新便可。

    a.下面的代碼是獲取數據咱們選中的數據並經過KVC把參數傳到目的視圖中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma mark - Navigation
//把對應的cell上的值傳到修改的頁面上
 
// In a storyboard-based application, you will often want to do a little preparation before navigation
- ( void )prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
     //參數sender是點擊的對應的cell
     //判斷sender是否爲TableViewCell的對象
     if  ([sender isKindOfClass:[UITableViewCell  class ]]) {
         //作一個類型的轉換
         UITableViewCell *cell = (UITableViewCell *)sender;
         
         //經過tableView獲取cell對應的索引,而後經過索引獲取實體對象
         NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
         
         //用frc經過indexPath來獲取Person
         Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
         
         //經過segue來獲取咱們目的視圖控制器
         UIViewController *nextView = [segue destinationViewController];
         
         //經過KVC把參數傳入目的控制器
         [nextView setValue:person forKey:@ "person" ];
     }
}

    b.在UpdateViewController中把傳過來的實體對象進行更新,再保存。更新部分的代碼和添加部分的代碼差很少,在這就不往上貼啦。

  通過上面的艱苦的歷程後咱們的tableView就會深深的愛上CoreData, 可能上面的內容有些多,有疑問的能夠留言交流。 

 

 

  上面所作的功能裏咱們的真正的通信錄還有些差距,看過上面的代碼的小夥伴會有個疑問:添加的頁面和更新的頁面能不能使用同一個呢? 固然啦,爲了遵循Don`t Repeat Yourself的原則,下面咱們就把兩個類似的頁面合併在一塊兒,同時給咱們每條記錄加上頭像和給整個tableView加上索引。

  1.把更新頁面刪掉,作以下修改,點擊添加和修改都跳轉到咱們的編輯頁面,同時添加一個自定義Button,點擊Button時,咱們會調用ImagePickerController來從手機相冊獲取圖片:

  2.爲了把頭像持久化存儲,咱們還得修改數據模型,重新生成Person類,添加一個存儲image的選項,是經過二進制的形式存儲的

  3.在以前保存的ViewController中若是Person爲空,說明是執行的添加記錄的方法咱們就生成一個新的person, 若是Person不爲空則不新建Person對象,直接更新完保存。

    (1)爲了獲取圖片,咱們須要添加ImagePickerController對象,並在viewDidLoad中作相應的配置,代碼以下

1
2
//聲明ImagePicker
@property (strong, nonatomic) UIImagePickerController *picker;

       進行相關配置

1
2
3
4
5
6
//初始化並配置ImagePicker
self.picker = [[UIImagePickerController alloc] init];
//picker是否能夠編輯
self.picker.allowsEditing = YES;
//註冊回調
self.picker.delegate = self;

 

    (2)點頭像會跳轉到咱們定義好的ImagePickerController中,咱們就可在圖片庫中選取相應的照片啦。

1
2
3
4
5
6
7
//點擊圖片按鈕設置圖片
- (IBAction)tapImageButton:(id)sender {
 
     //跳轉到ImagePickerView來獲取按鈕
     [self presentViewController:self.picker animated:YES completion:^{}];
 
}

 

    (3)在ImagePickerController中點擊取消按鈕觸發的事件,跳轉到原來編輯的界面

1
2
3
4
5
6
//回調圖片選擇取消
-( void )imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
     //在ImagePickerView中點擊取消時回到原來的界面
     [self dismissViewControllerAnimated:YES completion:^{}];
}

 

      (4)選完圖片把頭像設置成用戶選中的按鈕,並dismiss到原來界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//實現圖片回調方法,從相冊獲取圖片
-( void ) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
 
     //獲取到編輯好的圖片
     UIImage * image = info[UIImagePickerControllerEditedImage];
     
     //把獲取的圖片設置成用戶的頭像
     [self.imageButton setImage:image forState:UIControlStateNormal];
     
     //返回到原來View
     [self dismissViewControllerAnimated:YES completion:^{}];
 
}

    (5)把咱們點擊保存按鈕回調的方法做以下修改,若是person爲空,咱們會新建一個新的person.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (IBAction)tapSave:(id)sender
{
     //若是person爲空則新建,若是已經存在則更新
     if  (self.person == nil)
     {
         self.person = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Person  class ]) inManagedObjectContext:self.managedObjectContext];
     }
     //賦值
     self.person.name = self.nameTextField.text;
     self.person.tel = self.telTextField.text;
     self.person.firstN = [NSString stringWithFormat:@ "%c" , pinyinFirstLetter([self.person.name characterAtIndex:0])-32];
     
     //把button上的圖片存入對象
     UIImage *buttonImage = [self.imageButton imageView].image;
     self.person.imageData = UIImagePNGRepresentation(buttonImage);
     
     //保存
     NSError *error;
     if  (![self.managedObjectContext save:&error]) {
         NSLog(@ "%@" , [error localizedDescription]);
     }
     
     //保存成功後POP到表視圖
     [self.navigationController popToRootViewControllerAnimated:YES];
     
}

​    

    (6)由於是何更新頁面公用的因此咱們要在viewDidLoad對TextField和Button的背景進行初始化,若是person中的imageData有值咱們有用傳過來的圖片,不然用默認的圖片,添加數據初始化代碼以下:

1
2
3
4
5
6
7
8
9
self.nameTextField.text = self.person.name;
self.telTextField.text = self.person.tel;
 
if  (self.person.imageData != nil)
{
     UIImage *image = [UIImage imageWithData:self.person.imageData];
     [self.imageButton setImage:image forState:UIControlStateNormal];
 
}

​  

  

  4.上面的代碼就能夠插入頭像了,咱們須要在tableView中進行顯示便可,在tableView中從person對象中獲取相應的頭像,而後顯示便可,下面咱們要加上索引。

    (1)在cell中顯示頭像的代碼以下:

1
2
3
4
if  (person.imageData != nil) {
     UIImage *image = [UIImage imageWithData:person.imageData];
     cell.imageView.image = image;
}

    (2)實現添加索引回調的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//給咱們的通信錄加上索引,下面的方法返回的時一個數組
-(NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView
{
     //經過fetchedResultsController來獲取section數組
     NSArray *sectionArray = [self.fetchedResultsController sections];
     
     //新建可變數組來返回索引數組,大小爲sectionArray中元素的多少
     NSMutableArray *index = [NSMutableArray arrayWithCapacity:sectionArray.count];
     
     //經過循環獲取每一個section的header,存入addObject中
     for  ( int  i = 0; i < sectionArray.count; i ++)
     {
         id <NSFetchedResultsSectionInfo> info = sectionArray[i];
         [index addObject:[info name]];
     }
     
     //返回索引數組
     return  index;
}

​  通過上面的步驟,咱們以前倆個頁面能夠共用,並且加上了頭像和索引,運行效果以下:

 

   上面的內容挺多的啦吧,彆着急,咱們的這個通信錄還沒完呢,通信錄中的查詢功能是少不了的,由於當存的用戶多了,爲了方便用戶查詢咱們還須要添加一個控件。接下來是咱們Search Bar and Search 出場的時候了。UISearchDisplayController本身有一個TableView用於顯示查詢出來的結果,須要在通信錄中添加一些代碼咱們的Seach Bar就可使用了。

  1.在storyboard中添加Search Bar and Search,而後把屬性拖入咱們對應的TableViewController中便可,新添加屬性以下:

//添加Search Display Controller屬性
@property (strong, nonatomic) IBOutlet UISearchDisplayController *displayC;

  

  2.編輯SearchBar內容改變後調用的方法,咱們會經過用戶輸入的內容進行一個模糊查詢,把查詢的內容添加到咱們以前的fetchResultController中

複製代碼
 1 //當search中的文本變化時就執行下面的方法
 2 - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
 3 {
 4     //新建查詢語句
 5     NSFetchRequest * request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([Person class])];
 6     
 7     //排序規則
 8     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstN" ascending:YES];
 9     [request setSortDescriptors:@[sortDescriptor]];
10     
11     //添加謂詞
12     NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name contains %@",searchText];
13     [request setPredicate:predicate];
14     
15     //把查詢結果存入fetchedResultsController中
16     self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"firstN" cacheName:nil];
17     
18     NSError *error;
19     if (![self.fetchedResultsController performFetch:&error]) {
20         NSLog(@"%@", [error localizedDescription]);
21     }
22 }
複製代碼

 

  3.由於UISearchDisplayController裏的TableView和咱們以前的tableView用的是一個FetchedReaultsController,因此在UISearchDisplayController取消的時候要重載一下咱們以前的TableView,或去通信錄中的FetchedResultsController, 代碼以下:

//當在searchView中點擊取消按鈕時咱們從新刷新一下通信錄
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
   [self viewDidLoad];
}

 

  4.由於經過search查詢的結果集會顯示在UISearchDisplayController本身的tableView中,因此加載cell時要進行相應的選擇,search中的cell是咱們自定義的cell, 選擇代碼以下:

複製代碼
 1     //根據不一樣的tableView來設置不一樣的cell模板
 2     if ([tableView isEqual:self.tableView])
 3     {
 4         
 5         cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
 6         
 7     }
 8     else
 9     {
10         cell = [tableView dequeueReusableCellWithIdentifier:@"SearchCell" forIndexPath:indexPath];
11         
12     }
複製代碼

 

  5.在咱們的查詢後的列表中,若是還想點擊cell之後跳轉到編輯頁面,咱們該如何作呢? 添加下面的回調方法,用代碼進行跳轉,代碼以下:

複製代碼
 1 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
 2 {
 3     if ([tableView isEqual:self.displayC.searchResultsTableView])
 4     {
 5          Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
 6         UIStoryboard * s = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
 7         
 8         //獲取要目標視圖
 9         UIViewController *destination = [s instantiateViewControllerWithIdentifier:@"EditViewController"];
10         
11         //鍵值編碼傳值
12         [destination setValue:person forKeyPath:@"person"];
13         
14         [self.navigationController pushViewController:destination animated:YES];
15     }
16 }
複製代碼

 

  通過上面的步驟,咱們的查詢功能就寫好了,下面是最終的運行結果:

  經上面這麼曲折的過程,咱們的通信錄的基本功能就差很少了

相關文章
相關標籤/搜索