iOS教程:Core Data数据持久性存储基础教程
目錄[-]
- 創建Core Data工程
- 創建數據模型
- 測試我們的數據模型
- 來看看SQL語句的真面目
- 自動生成的模型文件
- 創建一個表視圖
- 之后看些什么?
?
就像我一直說的,Core Data是iOS編程,乃至Mac編程中使用持久性數據存儲的最佳方式,本質上來說,Core Data使用的就是SQLite,但是通過一系列特性避免了使用SQL的一些列的麻煩,不僅如此,他還能夠合理管理內存,反正好處很多,我們推薦使用。?
??
在這個教程中,我們將會創建一個Core Data的可視模型,之后再做一個Table View,讓Table View的內容能夠存儲在數據模型里。?
??
創建Core Data工程?
??
首先打開Xcode,新建一個工程,選擇Master-Detail模板。?
??
??
我們這里使用FailedBankCD作為工程名稱。?
??
記住一定要選中Use Storyboards,?Use Core Data, and?Use Automatic Reference Counting?這幾個選項,之后創建。?
??
在開始之前,我想先把一些沒有用的模板文件刪除掉,選中一下四個文件。?
- FBCDMasterViewController.h?
- FBCDMasterViewController.m?
- FBCDDetailViewController.h?
- FBCDDetailViewController.m?
刪除之,一了百了,選擇 “Move to Trash”。?
??
現在使用Obj-c class的模板新建一個文件,命名為FBCDMasterViewController?他是個UITableViewController,記住下面的幾個鉤都不能選。?
選中FBCDMasterViewController.h?在@end?之前的結尾行加入代碼:?
| @property (nonatomic,strong) NSManagedObjectContext* managedObjectContext; |
現在來到 .m 文件, 加入下面的語句。?
| @synthesize managedObjectContext; |
如果你不知道什么是 “NSManagedObjectContext” 那也沒關系,我們一會之后會談這問題。?
??
但是我們還是得先在Storyboard中刪除掉我們剛才所刪除的類所對應的視圖,看圖:?
??
??
最后,選中FailedBanksCD.xcdatamodel,你會看到出現了一個可視編輯器我們接下來就用這個編輯器來編輯我們的數據模型,選中中間顯示“Entity”(實體)的小泡泡,之后刪除之。?
??
如果你的編輯器看起來和下面的不一樣,那么請將編輯器風格(Editor Style)改成visual view?
??
??
OK,大功告成,如果現在啟動這個App,你會發現他就是個空白的應用。?
??
打開 FailedBanksCDAppDelegate.m,你會看到已經有一些預置的函數在這里了,這是為了建立Core Data的棧,包括如何創建數據模型,如何管理數據模型,如何建立持久性數據協調器等等。?
??
如果這些術語看的你頭疼,那也沒關系,看了下面的解釋你就會明白了:?
??
- Managed Object Model(管理數據模型): 你可以將這個東西看作是數據庫的輪廓,或者結構。這里包含了各個實體的定義信息,一般來說,你會使用我們剛剛看過的視覺編輯器來操作這個物體,添加屬性,建立屬性之間的關系等等,當然你也可以使用代碼。?
- Persistent Store Coordinator (持久性數據協調器):?你可以將這個東西看作是數據庫連接庫,在這里,你將設置數據存儲的名字和位置,以及數據存儲的時機。?
- Managed Object Context (管理數據內容):你可以將這一部分看作是數據的實際內容,這也是整個數據庫中對我們而言最重要的部分(這還用說),基本上,插入數據,查詢數據,刪除數據的工作都在這里完成。
創建數據模型?
??
Core Data與SQLite不同的是,你不能夠提取一個實體的某些屬性,你只能夠把整個實體提取出來,,之后再將他們分解。?
??
讓我們看看這是如何運作的,打開視覺編輯器(單擊FailedBanksCD.xcodedatamode)?
??
在底部的工具欄中,單機加號,新建一個實體。?
新建實體之后,視覺編輯器會顯示他的屬性?
將這個新實體命名為FailedBankInfo,之后單機這個實體,確保可視部分的第三個標簽都是被選中的。在屬性檢查器的第三個標簽里,你就會看到這個實體是NSManagedObject的子類,這是實體的默認類,我們現在先用著,將來我們會返回來修改為一個自定義的類。?
??
??
現在,讓我們加入一些屬性吧(Atrributes),確保你的實體是被選中的狀態,之后在中間的面板的底部按下加號,之后會出現一個像下面這樣的選項框。?
??
??
在Data Model屬性檢查器中,向下面那樣將這個屬性命名為“name”,type設置為“String”?
??
?
??
現在,再繼續增加兩個新的屬性“city”和“State”,類型都是字符串。?
??
接下來,我們創建一個名為FailedBankDetails的實體,你一定已經會弄了吧?之后增加下面的屬性在里面,zip(integer32屬性)closeDate(date屬性)updateDate(date屬性)。?
??
最后,我們將這兩種實體連接起來,選擇FailedBankInfo,按住中間面板的加號鍵不放,選擇 “Add relationship”:?
??
?
??
將這個relationship命名為“details”, 將目標設置為 “FailedBankDetails.”?
??
??
好了,我們剛剛設置了一個連接兩個實體的關系,這意味著,每一個FailedBankInfo的屬性都將會擁有一個一個FailedBankDetails的實體,在設置的背后,Core Data會自動設置FailedBankInfo中實體的ID,不過這些我們不需要了解。?
??
Apple官方建議說,每當你建立一個目標關系時,最好建立一個返回的關系,所以我們就按照官方的指示做吧。?
在“FailedBankDetails” 中建立一個叫做 “info”的關系,設置他的目標是 ?“FailedBankInfo”,正好與“Details”這個關系是反著的。?
??
??
接著,我們設置這兩個關系的刪除規則為“cascade”,這意味著,如果你刪除了其中一個數據,另一個實體中的數據也會跟著本刪除。?
??
運行一下,怎么?崩潰了???
??
??
哦,原來是之前已經存在的一個數據已經無法被我們修改過的數據模型讀取了,在這種情況下,只需要刪除模擬器中的app,再重新運行就可以了。?
??
??
測試我們的數據模型?
??
不管你怎么想,測試可是我們編輯數據庫中最重要的一步。?
??
首先,我們向我們的數據庫中加入一些測試數據,打開FailedBanksCDAppDelegate.m,在頂部application:didFinishLaunchingWithOptions方法中加入下面的代碼:?
| 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:@"updateDate"]; [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]); } |
在第一行,我們創建了一個指向我們的數據庫的指針。?
??
接著,我們為FailedBankInfo實體創建一個NSManagedObject實體,這里使用的是insertNewObjectForName的方法,每一個使用Core Data儲存數據的方法都是由NSManagedObject中衍生出來的,當你創建了這個方法的實例的時候,你就可以給我們剛才在視覺編輯器中創建的模型中的任何屬性進行賦值了。?
??
之后我們設置一個測試數據,在這里數據只在內存中被修改,要是想要存入數據庫,我們必須使用managedObjectContext方法?
插入一個數據就是這么簡單,完全不用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"]); } |
這里我們創建一個叫做fetch request的新方法,你可以將一個fetch request看做Sql中的select語句,我們調用entityForName方法來獲取一個指向FailedBankInfo的指針,之后使用setEntity方法來告訴我們的fetch request我們想要的是哪一種的實體。?
??
之后,我們調用executeFetchRequest方法,將FailedBankInfo表中的所有數據推入一個數據緩存中,之后枚舉所有NSManagedObject,使用valueForKey語句來調用其中的數據。?
??
注意盡管我們從FailedBankInfo表中推出了所有的數據,我們仍然可以通過FailedBankInfo實體中的詳細數據來訪問FailedBankDetails這個數據體。?
運行一下這個應用,目前你在屏幕上什么也看不到,你可以看看程序的輸出窗口,你就可以看到輸出結果了,每次你設置數據庫的時候都應該做這樣一個測試。?
??
來看看SQL語句的真面目?
??
我不知道你怎么想的,但是我個人喜歡看到每個語句后面的SQL語句,以確定這個程序正在按照我想的方式前進。?
??
Apple提供了一個這樣做的簡便的方法,看下圖,在Edit Scheme中選擇Run,之后進入Arguments標簽,加入下面的語句:“-com.apple.CoreData.SQLDebug 1”,完成之后,你會看到第二張圖:?
??
??
現在,每當你運行這個程序,Debug欄就會輸出正在進行的活動的SQL語句了。?
??
??
如果你對SQL語句不了解,也沒有關系,沒有人強制你學習這個煩人的東西,你可以一直使用Core Data。?
??
自動生成的模型文件?
??
目前為止,我們一直在用 NSManagedObject 來處理我們的實體,這并不是最好的方式,為什么呢?因為NSManagedObject不是一個 強型類(strongly typed class),所以,你只能夠使用字符串來訪問數據屬性,如果打錯了的話,就會造成錯誤,很不爽。?
??
更好的方法是為每一個實體都創建一個模型文件,這樣做好處很多,不贅述,Xcode提供了一個類生成器讓我們方便地完成這個任務。?
??
來試試看,打開?FailedBanksCD.xcdatamodel,點擊?FailedBankInfo?entity,進入File——NewFile。選擇Core DataNSManagedObject 模板,之后創建:
??
?
??
你應該可以看到,你的工程中添加了一些新的文件,就是FailedBankInfo.h/m 和 FailedBankDetails.h/m,這些是一些非常簡單的類,只是為了聲明你在實體中添加的屬性而已,Xcode會動態的添加語句進去,我們不用管這些。?
為FailedBankDetails 實體做同樣的事情。?
??
??
但是這些自動生成的類中有一個問題我們必須手動解決,如果你打開FailedBankDetails.h來看,你就會發現info變量被聲明稱了FailedBankInfo,但是在FailedBankInfo.h中,他被聲明稱了NSManagedObject類,其實應該是FailedBankDetails類。?
??
??
這是因為FailedBankInfo的生成器在FailedBankDetails的生成器之前運行,所以生成器不知道后面還有個這玩意。?
你可以像上面那樣手動修復,但是也有簡單的方法,就是重新運行FailedBankInfo的生成器。?
如果我們回到FailedBanksCD.xcdatamodel,當你查看實體的類的時候,你會發現他們已經被自動修改為自動創建的類。?
??
??
為了測試,打開BCDAppDelegate.m?,在頭部加入:?
| #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.updateDate = [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); } |
這和我們上次測試的代碼差不多,是不是??
??
創建一個表視圖?
??
打開 FBCDMasterViewController.h 聲明一個數組,你懂得(如果不懂請見《如何創建一個簡單的表》)?
| @property (nonatomic, strong) NSArray *failedBankInfos; |
請注意,我們在測試的時候已經使用了一些方法來獲取數據了,就在FBCDAppDelegate.m中的application:didFinishLaunchingWithOptions方法中,記得嗎??
??
回到FailedBanksListViewController.m 作出以下修改:?
| // At very top, in import section #import "FailedBankInfo.h"// At top, under @implementation @synthesize failedBankInfos; |
修改 viewDidLoad 方法:?
| - (void)viewDidLoad {[super viewDidLoad]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; NSError *error; self.failedBankInfos = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; self.title = @"Failed Banks"; } |
這和我們測試的代碼差不多是不是?我們使用一個 fetch request 來獲取數據庫中的數據,之后儲存在內存中。?
??
numberOfSectionsInTableView:?
| - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {return 1; } |
numberOfRowsInSection:?
| - (NSInteger)tableView:(UITableView *)tableViewnumberOfRowsInSection:(NSInteger)section {return [failedBankInfos count]; } |
cellForRowAtIndexPath :?
| - (UITableViewCell *)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *CellIdentifier = @"Cell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // 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; } |
以上是制作一個表視圖的代碼,很熟悉吧,接下來我們在Storyboard中制作這個表視圖,記得將cell的style設置為subtitle。?
??
??
運行一下這個App,看看吧。?
??
??
之后看些什么??
這是我制作完成的例子程序源碼,歡迎下載。?
這是原作者的樣板程序:?sample code for the project so far?(direct download).
總結
以上是生活随笔為你收集整理的iOS教程:Core Data数据持久性存储基础教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入浅出Mybatis系列(一)---M
- 下一篇: 一个屌丝程序猿的人生(十九)