原文:http://www.dasheyin.com/ios_jiao_cheng_ru_he_shi_yong_core_data_-_yu_jia_zai_he_yin_ru_shu_ju.html
是接着上一次《iOS教程:Core Data數據持久性存儲基礎教程》的後續教程,程序也會使用上一次製作完成的。
再上一個教程中,我們只做了一個數據模型,之後我們使用這個數據模型中的數據創建了一個表視圖,我們還學習瞭如何測試數據模型的可行性,今天,我們來看看如何在應用啓動的時候,將已經存在的數據載入或者引用到我們的程序中去。
請注意我們在上一次的教程中學習到的是直接通過操作SQLite數據庫來加載數據,你當然可以一直使用這種方法,但是這個教程教授的方法更加優雅,更加合理。
在下一部分的教程中,我們將會討論如何使用NSFetchedResultsController來優化我們的應用的訪問數據的方式。
至於如果你沒有上一次做好的程序的話,你可以從這裏下載。
預加載/引入數據
那麼我們究竟怎樣把數據存儲進Core Data數據庫呢?目前有兩種比較好的選擇。
- 在App啓動的時候從外部文件引入數據,就是在程序開始運行的時候從外部的資源,比如SQLite數據庫或者XML文件中,引入數據。
- 提供一個已經制作完成的SQLite數據庫,首先製作一個像上次的教程說的那樣的數據庫模型,之後在這個模型中填充數據,填充數據的方式是使用一個utility app,這個utility app可以是一個使用Core Data API填充數據庫的Mac或者iOS app,也可以是一些直接填充數據庫的程序。一旦數據庫被填充之後,你就可以在沒有已存在的數據庫的情況下設置這個數據庫未使用的默認數據庫。
在這個教程中,我們會通過第二種,爲大家展示如何使用一個簡單的utility app來預加載一個已經裝在好的Core Data數據庫,以便讓你的app使用。
第一步
我們在iOS上使用Core Data的方法的基礎和我們在Mac OS X上使用的是一致的,他們使用同樣的模型和類。
這一爲我們可以寫一個MAC OS X上的簡單的console程序,來從數據源引入數據,再把這個數據庫的數據庫拿來給我們的iOS程序來用,不錯吧?
我們來試試,首先打開Xcode,在 Mac OSX類中的Application中使用Command Line Tool 的模板。
我們就用 “CoreDataTutorial2” 作爲工程的名字吧,記得使用“Core Data” 和 “Use Automatic Reference Counting” 。
完成創建之後,選擇 “CoreDataTutorial2.xcdatamodeld” 徹底刪除之。
之後找到我們上次完成的哪些文件中的
- FailedBankCD.xcdatamodeld
- FailedBankInfo.h
- FailedBankInfo.m
- FailedBankDetails.h
- FailedBankDetails.m
將這些文件複製,或者直接拖到我們的新項目中:
確保“Copy items into destination group’s folder (if needed)” 沒有選中
並且選中“Add to targets” 。
選擇 main.m,你會注意到由於我們選擇了使用Core Data,所以這裏爲我們準備了一些模板的方法,現在,我們來修改這些方法來讓他們爲我們的iOS程序生成數據數。
將 managedObjectModel()
方法從
NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]; path = [path stringByDeletingPathExtension]; |
替換爲
NSString *path = @"FailedBankCD"; |
這會把這個程序指向 FailedBankCD.xdatamodeld
而不是我們已經刪除的CoreDataTutorial2.xdatamodeld
。
按下command+r進行編譯和運行,應該看到沒有錯誤。
但是如果你在這一步之前進行過編譯的話,你到這時就會出現數據不符的錯誤,按照我們上次的教程所說的那樣,刪除之後重新編譯運行就行。
如果你看到了下面的錯誤:
NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator with a nil model'
這是因爲程序再找一個 ‘momd’ 文件, (上一個版本的Core Data模型),但是如果你的app使用的不是這個文件的話,那就會出這個錯誤,最快的修正方法就是把managedObjectModel()這個方法修改爲下面的:
現在應該就沒問題了
引入數據
現在到了動真傢伙的時候了,真的把我們的數據加載進去。
在我們這個例子裏,我們要從一個JSON文件中引入數據,也許你會想從其他類型的文件中引入數據,不過原理都是一樣的。
下面,我們新建一個文件iOS – Other – Empty
把這個文件命名爲Banks.json。
將下面的代碼輸進去:
[{ "name": "Bank1", "city": "City1", "state": "State1", "zip": 11111, "closeDate": "1/1/11" }, { "name": "Bank2", "city": "City2", "state": "State2", "zip": 22222, "closeDate": "2/2/12" }, { "name": "Bank3", "city": "City3", "state": "State3", "zip": 33333, "closeDate": "3/3/13" }, { "name": "Bank4", "city": "City4", "state": "State4", "zip": 44444, "closeDate": "4/4/14" } ] |
這是一個一個數組中包含四個字典的JSON文件,每一個字典都有幾個與FailedBankInfo/FailedBankDetails中的物體相對應的屬性。
如果你不是很清楚JSON文件是如何組織數據的,你可以看一下這個教程: this tutorial.
接下來,我們告訴我們的應用當編譯的時候將這個文件我們的產品目錄,看圖做,首先選擇Project,之後選擇CoreDataTutorial2目標,選擇Build Phase選項卡,按下“Add Build Phase”,選擇“Add Copy File”,選擇目標位置爲“Products Directory”,最後,把“Banks。json拖到Add Files的部分。
當一個應用啓動時,要先使用FailedBank的數據模型和類初始化一個Core Data的數據庫,之後用Banks.json文件中的數據來輸進去。
現在,我們要:
- 載入 JSON 文件
- 解析 JSON 文件爲一個 Objective C 數組
- 枚舉這個數組中的數據,爲每一個物體創建一個managed item。
- 將他們全都存入 Core Data
編程開始,首先打開 main.m 把下面的代碼加入主函數:
NSError* err = nil; NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"]; NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath] options:kNilOptions error:&err]; NSLog(@"Imported Banks: %@", Banks); |
之後你的主函數看起來應該是下面這個樣子的:
int main(int argc, const char * argv[]) { @autoreleasepool { // Create the managed object context NSManagedObjectContext *context = managedObjectContext(); // Custom code here... // Save the managed object context NSError *error = nil; if (![context save:&error]) { NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error"); exit(1); } NSError* err = nil; NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"]; NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath] options:kNilOptions error:&err]; NSLog(@"Imported Banks: %@", Banks); } return 0; } |
這是用了使用了內置的 NSJSONSerialization API 來簡單地將JSON的文件數據導入Core Foundation的數據類型中區(如NSArray,NSDictionary等等),想了解更多的話,請看 this tutorial.
運行一下這個程序,你會看到下面的輸出:
2012-04-14 22:01:34.995 CoreDataTutorial2[18388:403] Imported Banks: ( { city = City1; closeDate = "1/1/11"; name = Bank1; state = State1; zip = 11111; }, { city = City2; closeDate = "2/2/12"; name = Bank2; state = State2; zip = 22222; }, { city = City3; closeDate = "3/3/13"; name = Bank3; state = State3; zip = 33333; }, { city = City4; closeDate = "4/4/14"; name = Bank4; state = State4; zip = 44444; } ) |
現在我們已經能夠把這些數據存儲進了一個Objective – C的物體中,那麼現在我們就可以像上次教程的末尾那樣把這些數據輸入進Core Data的數據庫中。
首先在頭部加上一下你需要的文件的引用語句:
#import "FailedBankInfo.h" #import "FailedBankDetails.h" |
之後把這些你之前加入主函數代碼。
[Banks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { FailedBankInfo *failedBankInfo = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankInfo" inManagedObjectContext:context]; failedBankInfo.name = [obj objectForKey:@"name"]; failedBankInfo.city = [obj objectForKey:@"city"]; failedBankInfo.state = [obj objectForKey:@"state"]; FailedBankDetails *failedBankDetails = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankDetails" inManagedObjectContext:context]; failedBankDetails.closeDate = [NSDate dateWithString:[obj objectForKey:@"closeDate"]]; failedBankDetails.updateDate = [NSDate date]; failedBankDetails.zip = [obj objectForKey:@"zip"]; 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); } |
這些代碼本質上就是我們上一次使用的代碼,除了我們這次使用了enumerateObjectsUsingBlock: 的方法老枚舉這個數組的內容之後進行插入,之後我們使用一個Fetch命令來輸出數據。
現在運行一下,你會看到輸出了之前的數組。
2012-04-14 22:15:44.149 CoreDataTutorial2[18484:403] Name: Bank1 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Zip: 11111 2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Name: Bank2 2012-04-14 22:15:44.151 CoreDataTutorial2[18484:403] Zip: 22222 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Name: Bank3 2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Zip: 33333 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Name: Bank4 2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Zip: 44444 |
Ok,這些就是你在Core Data中的數據了。除了這種簡單的JSON文件之外,你也可以使用更加複雜的JSON文件,XML文件,甚至是普通的表格文件,只要你存成了csv的格式,也可以是來自互聯網的pipe,可以使用的文件種類數也數不清,我們以後也會詳細介紹。
Xcode犯病了?
下面我們做一個腦部移植手術,將我們使用Mac OS X上的命令行程序的數據庫轉移到iPhone app中去。最簡單的找到數據庫文件的方法就是右鍵(ctrl+) CoreDataTutorial2
產品攬之後按
“Show in Finder”。
這會打開一個新的Finder窗口,在這裏面會有這些文件:
- Banks.json – 這是數據的原始文件,記得嗎?
- CoreDataTutorial2 – 這個是應用本身。
- FailedBankCD.momd (或者 .mom) – 這是編譯好的Core Data數據模型。
- CoreDataTutorial2.sqlite – 這就是我們在找的sqlite數據庫文件,它是由程序生成的,Core Data應該可以通用的。你可以自己找一個SQLite數據庫的查看軟件,也可以下載 這個
確定 “CoreDataTutorial2.sqlite” 就是我麼所需要的文件,下面我們把這個文件拷貝到我們上一個教程的源碼工程文件之中,之後打開:
從Finder中拖拽 “CoreDataTutorial2.sqlite” 文件到Xcode的工程之中,確保 “Copy items into destination group’s folder (if needed)” 這個選項沒有被選中,另一個是選中的。
最後,打開 “FBCDAppDelegate.m”,找到 persistentStoreCoordinator
方法,在 NSURL
*storeURL = [[self app...
這一行的下面加入以下的代碼:
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) { NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"CoreDataTutorial2" ofType:@"sqlite"]]; NSError* err = nil; if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) { NSLog(@"Oops, could copy preloaded data"); } } |
這一段的代碼是爲了檢測sqlite數據庫是否已經存在與這個app之中,如果不存在,就會找到我們預加載的數據庫,之後把這個數據庫複製到正常的路徑,超級簡單,來,讓我們試試!
看到了原本在JSON文件中的四個Banks,之後還有一個我們在第一個教程中加入的Test Bank,如果你沒有看到的話,八成是數據庫已經存在了,山茶模擬器中的App之後重新運行。