開始學習CoreData ,但仍是一知半解的。看了蘋果官方代碼,也搜了蠻多網上的,但還不是很理解。
內容轉自以下四個博客地址,說的是同一個內容,但是第一個不全,第二個用不同方法,第三個不能復制,第四個沒圖。。 總的都放在一起好了。
孫啟超_Christ: http://qing.weibo.com/1565154467/5d4a5ca333001le8.html
http://www.dasheyin.com/ios_jiao_cheng_core_data_shu_ju_chi_jiu_xing_cun_chu_ji_chu_jiao_cheng.html
http://www.360doc.com/content/11/0715/17/6890766_133761773.shtml
http://blog.sina.com.cn/s/blog_74625ffd0100ss2k.html
有些理解,但還沒多大感觸。
在iPhone所有數據存儲的方法里面,Core Data是重要數據存儲的最佳選擇。它能降低你應用的內存開銷,提升響應速度,並把你從繁瑣的代碼中解脫出來。
然而,學習Core Data之路異常久遠。不過這也是這一系列教程的由來 – 讓你快速掌握Core Data基礎知識。
作為該系列教程的第一部分,我們將為我們的對象建立一個可視化數據模型。為保證其有效性,我們會做一個快速骯臟測試(dirty test驗證其健壯性和有效性),然后將其勾在一個表視圖(table view)里,這樣我們可以看到這一列對象。
系列教程的第二部分,我們將討論如何將數據預先載入到Core Data中,這樣我們的應用啟動時就初始化好了。
最后的部分,我們將討論如何使用NSFetchedResultsController來優化應用,達到降低內存開銷以及改進響應時間的目的。
在本教程開始之前,我建議先翻閱我以前寫的SQLite for iPhone Developers教程,這會讓你更容易理解。另外,2個教程做的應用(app)都是一樣的,只是這次我們用的是Core Data!
創建一個Core Data工程
下面讓我們開始!建立一個新的Window-based Application,勾選“Use Core Data for storage”,將工程命名為“FailedBanksCD.”
在開始前,我們快速看一下建好的工程。首先展開Resources並雙擊FailedBanksCD.xcdatamodel,會彈出一個可視化編輯器-這就是我們接下來會用到的模型對象圖示。現在我們先把它關閉。
然后看看FailedBanksCDAppDelegate.m。在這里你會看見已經為我們實現的一些新函數,用於建立Core Data”堆”。新增了一個 managed object context,一個managed object model, persistent store coordinator. 啊??
別擔心。名字雖然開始聽起來奇怪,一旦你打通“任督二脈”就很容易理解到他們是什么。
Managed Object Model:你可以把它當作是數據庫規划圖。這是一個包含其他對象(也叫Entities)定義的類,對象存儲在數據庫里面。通常會用剛剛瞄過的可視化編輯器來設定數據庫的對象,它們的特性(attributes),它們是如何相互關聯的。然而,你也可以用代碼來實現。
Persistent Store Coordinator:你可以把它當作是數據庫的連接。在這里你設定用來存儲對象的數據庫的真實名稱和位置,以及需要時用來保存的管理對象上下文(managed object context)。
Managed Object Context:你可以把它當作是一個數據庫取出對象的“暫存器”。對我們來說它也是這三個里面最重要的,因為我們大多都在上面工作。基本上,無論何時需要獲取對象,插入對象或是刪除對象,你都要調用管理對象上下文(managed object context)的方法(或者說大部分時間!)。
無需過於擔心這些方法 – 你不用過於糾纏這些。相反,了解它們是什么以及它們代表的涵義會比較好。
定義我們的模型
SQLite教程里,當我們建立數據庫表的時候,我們用一個表包含了failed bank里面的所有數據。為了降低數據量在內存中的耗費(秉着學習的目的),我們僅僅拿出了需要子字段顯示在表視圖(table view)里面。
因此我們不禁會在Core Data中做同樣的嘗試。然而,使用Core Data你無法檢索一個對象中的單一特性(attributes),你只能檢索整個對象。但是,只要我們把這個對象分解成兩塊- FailedBankInfo和FailedBankDetails-我們就能實現同樣的目標。
所以讓我們看看如何實現。打開可視化編輯器(展開Resources並雙擊FailedBanksCD.xcdatamodel)。
從我們的模型中建立一個對象開始-用Core Data術語來說就是“Entity”。在左上面板,單擊+號來增加一個新的實體(Entity),如下:

按下+以后,會創建一個新的實體(Entity),然后在右邊的面板顯示實體(Entity)的屬性(properties),如下:

將實體(Entity)命名為FailedBankInfo。注意到它目前是NSManagedObject的子類。這是所有實體(Entity)的默認類,我們現在用的也是。等下我們會回來改成自定義類。
我們再來添加一些特性(attributes):首先,確保在左邊面板中選中相應的實體(entity),然后在中間的面板中點擊+號,選中“Add Attribute”,如下:

在右邊的屬性(property)面板,將特性(attribute)命名為“name”,類型設為“String”,如下:

現在,重復添加2個特性(attributes), “city”和“state”, 類型都是string.
最后,我們需要連接這2種類型。選擇FailedBankInfo,單擊中間面板的+號,但這次選擇“Add relationship”:

把關系命名為“details”,然后設置其destination為“FailedBankDetails”。好,我們剛剛做了什么?我們在Core Data中建立了關系,把兩個實體(entity)連接在了一起。在這種情況下,我們建立了一對一的關系-每個FailedBankInfo恰好對應一個FailedBankDetails。在此情景下,Core Data將設定我們的數據庫使得FailedBankInfo表擁有一個字段來對應FailedBankDetails對象的ID。
Apple建議,每當你建立一個到其他對象的連接,最好也從另外一個對象建立一個反向連接。所以我們也這么做。
現在添加一個關系到“FailedBankDetails”,取名為“info”,設置其destination到“FailedBankInfo”,然后inverses到“details”.
同樣,設置兩個關系的delete rule為“cascade”。意思是如果你用Core Data刪除一個對象,Core Data會把另外一個對象也刪除。這在此處是說得通的,因為一個沒有FailedBankInfo的FailedBankDetails是沒有任何存在價值的。
測試我們的模型
不管你信不信,這可能是我們要做的事當中最重要的。接下來就測試下Core Data是否管用。首先,在我們的數據庫中加入測試對象。打開FailedBanksCDAppDelegate.m並在applicationDidFinishLaunching最前面加入:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *failedBankInfo = [NSEntityDescription
insertNewObjectForEntityForName:@"FailedBankInfo"
inManagedObjectContext:context];
[failedBankInfo setValue:@"Test Bank" forKey:@"name"];
[failedBankInfo setValue:@"Testville" forKey:@"city"];
[failedBankInfo setValue:@"Testland" forKey:@"state"];
NSManagedObject *failedBankDetails = [NSEntityDescription
insertNewObjectForEntityForName:@"FailedBankDetails"
inManagedObjectContext:context];
[failedBankDetails setValue:[NSDate date] forKey:@"closeDate"];
[failedBankDetails setValue:[NSDate date] forKey:@"updatedDate"];
[failedBankDetails setValue:[NSNumber numberWithInt:12345] forKey:@"zip"];
[failedBankDetails setValue:failedBankInfo forKey:@"info"];
[failedBankInfo setValue:failedBankDetails forKey:@"details"];
NSError *error;
if (![context save:&error]) {
NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}
在第一行,我們通過模板的幫助取得了一個指針,該指針指向我們的管理對象上下文(managed object context)。
之后我們為FailedBankInfo實體(entity)創建一個新NSManagedObject實例,憑借的是對insertNewObjectForEntityForName的調用。Core Data存儲的每個對象都繼承自NSManagedObject。一旦你得到了對象的實例,你就可以調用setValue來設置你在可視化編輯器里面定義的對象的任一特性(attribute)。
接着我們為FailedBankInfo和FailedBankDetails建一個銀行信息做測試。這時對象剛剛在內存中被修改 – 要存回數據庫的話需要在管理對象上下文中(managedObjectContext)調用save。
我們就在這插入對象-一點兒也不需要SQL語句!
開始嘗試之前,我們來加入更多代碼來列舉數據庫現有的對象:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"FailedBankInfo" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
NSLog(@"Name: %@", [info valueForKey:@"name"]);
NSManagedObject *details = [info valueForKey:@"details"];
NSLog(@"Zip: %@", [details valueForKey:@"zip"]);
}
[fetchRequest release];
在這里我們建立一個新的對象,我們稱之為fetch request. 你可以把一個獲取請求看作是一條SELECT語句。我們調用entityForName來得到一個指向FailedBankInfo實體的指針,然后用setEntity來告知獲取請求這就是我們想要的。
之后對管理對象上下文(managed object context)調用executeFetchRequest來拉取FailedBankInfo對象的所有表單並放在“暫存器”中。然后我們遍歷NSManagedObject,並使用valueForKey來拉取各種字段。
注意到盡管我們只是拉取FailedBankInfo表單中的對象,我們仍然可以訪問相關聯的FailedBankDetails對象,通過訪問FailedBankInfo的details屬性(property)。
這是如何起作用的呢?當你訪問這個屬性(property)的時候,Core Data注意到當前上下文沒有這一數據,然后“報錯”,這一錯誤基本上意味着它已經跳到那個數據庫並拉取了你所需的正確內容。非常方便!運行這段代碼,看看你的輸出窗口,每次運行的時候你都會在你的數據庫中看到一個示例用銀行。
看看原始的SQL語句
我不清楚你是怎樣,但是我在處理這方面事務的時候特別喜歡看到實實在在的SQL語句來觀察進展(並確定事情如我所願)
Apple再一次提供了簡單的解決方案。在Xcode中打開Executables下拉列表並找到可執行的FailedBanksCD。右鍵並點擊“Get Info”。導航到Arguments標簽(tab)並增加以下語句:“-com.apple.CoreData.SQLDebug 1″。完成的結果如下:

現在運行代碼,在調試輸出窗口你可以看到一些有用的跟蹤語句,提醒你現在發生的事情:
於是我們可以看到事情正如我們所期望的那樣。前面2條select以及update語句表明Core Data正在做記錄以維持實體(entity)的下一個ID。
1
2
3
4
5
6
7
8
9
10
11
SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ?
UPDATE Z_PRIMARYKEY SET Z_MAX = ? WHERE Z_ENT = ? AND Z_MAX = ?
INSERT INTO ZFAILEDBANKDETAILS(Z_PK, Z_ENT, Z_OPT, ZINFO,
ZUPDATEDDATE, ZZIP, ZCLOSEDATE) VALUES(?, ?, ?, ?, ?, ?, ?)
INSERT INTO ZFAILEDBANKINFO(Z_PK, Z_ENT, Z_OPT, ZDETAILS, ZNAME,
ZSTATE, ZCITY) VALUES(?, ?, ?, ?, ?, ?, ?)
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY, t0.ZDETAILS
FROM ZFAILEDBANKINFO t0
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZUPDATEDDATE, t0.ZZIP, t0.ZCLOSEDATE,
t0.ZINFO FROM ZFAILEDBANKDETAILS t0 WHERE t0.Z_PK = ?
然后有對details和info表操作的insert into語句。隨后,我們在查詢中select整個bank info表。然后遍歷結果.每當訪問details變量的時候,Core Data 報錯並在另一個表ZFAILEDBANKDETAILS中執行select語句。
自動產生模式文件
在此之前,我們已經使用了NSManagedObject來操作我們的實體(Entities)。這其實不是最好的辦法,因為這樣很容易寫錯特性(attribute)名或者搞錯數據的類型等等。
更好的方式是為每個實體(entity)建立一個模型(Model )文件。你可以手工實現,但是用XCode類生成器會更簡單。
讓我們來試試看。打開FailedBanksCD.xcdatamodel,單擊FailedBankInfo實體(entity),然后去到File\New File。選擇“Cocoa Touch Class”,你會看到一個關於“Managed Object Class”的新模板。選擇它並單擊Next,再Next到下一視圖。

在第三個視圖中,XCode允許你選擇實體來產生類。為了節約時間,確保FailedBankInfo和FailedBankDetails都被選中,然后單擊Finish。
你可以看到一些新文件添加到了你的工程中。FailedBankInfo.h/m以及 FailedBankDetails.h/m。這些類非常簡單,僅僅根據你剛剛添加的實體做出的屬性(properties)聲明。你會注意到這些屬性(properties)在.m文件中聲明為dynamic,這是因為Core Data會自動連接這些屬性(properties)。
我有注意到自動產生類的一個問題並修正了它。如果你檢查一下FailedBankDetails.h,你會發現info變量正確地聲明為FailedBankInfo類,而FailedBankInfo.h 中的details變量則定義成了一個NSManagedObject(但本應是一個FailedBankDetails對象)。你可以通過在FailedBankDetails文件的頂端加入預聲明來修正這一錯誤。
<
@class FailedBankDetails;
之后如下改變details的屬性(property)聲明:
@property (nonatomic, retain) FailedBankDetails * details;
同樣,回頭瞧下FailedBanksCD.xcdatamodel.當你查看實體的屬性(property)時,你會發現它的類名已經自動設為你剛剛在自動生成類里面更改的內容了。

現在,讓我們調整app委托的測試代碼,用上新的NSManagedObject子類。首先導入頭文件:
#import "FailedBankInfo.h"
#import "FailedBankDetails.h"
然后跟下面一樣改變代碼:
NSManagedObjectContext *context = [self managedObjectContext];
FailedBankInfo *failedBankInfo = [NSEntityDescription
insertNewObjectForEntityForName:@"FailedBankInfo"
inManagedObjectContext:context];
failedBankInfo.name = @"Test Bank";
failedBankInfo.city = @"Testville";
failedBankInfo.state = @"Testland";
FailedBankDetails *failedBankDetails = [NSEntityDescription
insertNewObjectForEntityForName:@"FailedBankDetails"
inManagedObjectContext:context];
failedBankDetails.closeDate = [NSDate date];
failedBankDetails.updatedDate = [NSDate date];
failedBankDetails.zip = [NSNumber numberWithInt:12345];
failedBankDetails.info = failedBankInfo;
failedBankInfo.details = failedBankDetails;
NSError *error;
if (![context save:&error]) {
NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (FailedBankInfo *info in fetchedObjects) {
NSLog(@"Name: %@", info.name);
FailedBankDetails *details = info.details;
NSLog(@"Zip: %@", details.zip);
}
[fetchRequest release];
這跟我們之前寫的代碼很像,除了用新的子類替代直接NSManagedObject的部分。現在我們安全又簡潔的代碼完成了!
創建一個表單視圖
右鍵Classes並點擊“Add\New File…” 選擇“UIViewController subclass”,確保“UITableVIewController subclass”被選中,“With XIB for user interface”不被選中。將類命名為FailedBanksListViewController。打開FailedBanksListViewController.h並添加2個成員變量:
一個我們用來從數據庫檢索的failedBankInfos成員變量/屬性(property),正如我們上次用的那樣。
一個用於管理對象上下文(managed object context)的成員變量。注意到盡管我們可以從應用程序的委托里面檢索它,最好還是傳遞給一個變量來避免相互依賴。
完成后代碼如下所示:
#import <UIKit/UIKit.h>
@interface FailedBanksListViewController : UITableViewController {
NSArray *_failedBankInfos;
NSManagedObjectContext *_context;
}
@property (nonatomic, retain) NSArray *failedBankInfos;
@property (nonatomic, retain) NSManagedObjectContext *context;
@end
跳轉到FailedBanksListViewController.m並加入一些import,你的synthesize語句,以及清理代碼。
// At very top, in import section
#import "FailedBankInfo.h"
// At top, under @implementation
@synthesize failedBankInfos = _failedBankInfos;
@synthesize context = _context;
// In dealloc
self.failedBankInfos = nil;
self.context = nil;
然后取消viewDidLoad的注釋並改成下面的樣子:
- (void)viewDidLoad {
[super viewDidLoad];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"FailedBankInfo" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSError *error;
self.failedBankInfos = [_context executeFetchRequest:fetchRequest error:&error];
self.title = @"Failed Banks";
[fetchRequest release];
}
這段代碼應該看起來很像早前的測試代碼。我們簡單的創建了一個獲取請求來得到數據庫中FailedBankInfos的全部內容,然后存儲到我們的成員變量里面。
其余改動就跟我們在SQLite教程中所做的一樣。為了快速參考,我將再次列出剩下的步驟。
對numberOfSectionsInTableView返回1:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
用以下代碼替代numberOfRowsInSection:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
FailedBankInfo *info = [_failedBankInfos objectAtIndex:indexPath.row];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@",
info.city, info.state];
return cell;
}
對我們剛剛加進來的UINavigationController ,在FailedBanksCDAppDelegate.h中增加一個outlet
@interface FailedBanksCDAppDelegate : NSObject <UIApplicationDelegate> {
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
UIWindow *window;
UINavigationController *_navController;
}
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navController;
- (NSString *)applicationDocumentsDirectory;
@end
打開Resources並雙擊MainWindow.xib。在庫中拖出一個Navigation Controller。單擊下箭頭,選中View Controller,在特性(attribute)面板的第四個標簽(tab)中將“Class”改成“FailedBanksListViewController”。

最后,按住control用鼠標(或者右鍵)從MainWindow.xib中的“FailedBanksCD App Delegate”拖到“Navigation Controller”,並連接其“navController”outlet。保存xlib並關閉。
現在我們只需要在FailedBanksCDAppDelegate.m中添加幾行代碼:
// At top
#import "FailedBanksListViewController.h"
// Under @implementation
@synthesize navController = _navController;
// In applicationDisFinishLaunching, before makeKeyAndVisible:
FailedBanksListViewController *root = (FailedBanksListViewController *) [_navController topViewController];
root.context = [self managedObjectContext];
[window addSubview:_navController.view];
// In dealloc
self.navController = nil;
編譯工程並運行,如果一切順利的話你會看到下面的示例銀行:

下一步要做什么
這里是迄今為止的示例代碼
到目前為止一切尚稱良好—除了failed banks的真實數據。所以系列的下一教程我們會涉及 如何預載/導入已有數據!
下面這個 如何預載/導入已有數據!的連接已經沒用了,看下一篇隨筆好了。