[iOS] 在iOS裡操作SQLite筆記- CoreData Framework(ORM) (二)
上一篇再取出SQLite的資料的時候,我們用底下這個方法
- (NSArray *) statementtoMSarray:(sqlite3_stmt *) sqlstmt{…}
來將資料回傳成一個陣列。這有點像早期寫ASP的時候,當我們送出一個Conn.execute物件會回傳一個
Recordset物件,然而要存取這個物件,利用上一篇的做法就比較像以往在存取一個RS物件,在這樣的存取資料方式,就會屬於弱型別的資料存取方式。
這是一個動態陣列物件,會多做這個將statement轉換成陣列物件的動作,是因為範例中我們要把資料塞去給UITableView。
- (NSArray *) statementtoMSarray:(sqlite3_stmt *) sqlstmt{
TabelDataes = [[NSMutableArray alloc]init];
NSString *data;
//這邊使用While迴圈來跑資料,把statement裡的資料一筆筆存進陣列裡面
while (sqlite3_step(sqlstmt) == SQLITE_ROW) {
data = [NSString stringWithUTF8String:(char *)sqlite3_column_text(sqlstmt,0)];
[TabelDatas addObject:data];
}
return TabelDatas;
}
看完上述資料,可以發現這種弱型別的存取方式,如果當Statement不單只是一維陣列的時候,你就要利用sqlite3_column_text(sqlstmt,0) , sqlite3_column_text(sqlstmt,1) , sqlite3_column_text(sqlstmt,2) 這樣的語法來取出相對應的資料。當資料維度一變大,在維護上就不比較不容易閱讀。接著要討論的是在iOS裡存取SQLite的ORM Framework架構Core data。
Core Data 主要分成下列個部分與運作流程
Managed Object Context : 這是CoreData 所有部分中對我們最重要的,這個物件是當我們要對Sqlite去新增,刪除,修改,查詢這些動作時,我們需要呼叫此類別裡面的方法。Managed Object Context 這個類別記載了這個App在記憶體中的所有Entity。當我們向Core Data 要求載入物件(Managed Object,一筆資料時),會先向Managed Object Context提出要求。假如記憶體中沒有我們要求的這筆資料,Managed Object Context 擇向Persistent Store Coordinator發出請求。Persistent Store Coordinator會追蹤管理 Persistent Object Store 物件對資料庫的讀寫。
Persistent Store Coordinator: 可以想像成資料庫的連線物件,我們會在這裡去設定SQLite資料庫的實際位置跟名稱。告知應用App資料要被我們儲存到哪一個資料庫去。當Managed Object Context要對資料庫做任何的操作動作,CoreData底層都會呼叫這個物件。
Managed Object Model: 這部分可以想像它是database schema,它是一個儲存在資料庫裡面的資料描述類別(或稱之為Entities)。當App對Managed Object Context提出資料存取要求,Managed Object Context 擇向Persistent Store Coordinator發出請求。Persistent Store Coordinator會追蹤管理 Persistent Object Store 物件對資料庫的讀寫。而Persistent Object Store 物件取得資料庫的資料後,Persistent Store Coordinator物件會呼叫Managed Object Model 來依據 Data Model 的 Entity 類別生成 Managed Object物件並且載入記憶體當中,此時App就可以透過Managed Object Context物件所提供的方法對記憶體中的物件做操作,這個操作是離線的,所以如果要對資料庫更新這筆資料,就需要呼叫儲存的動作。
流程了解後來新建一個iOS CoreData專案做測試
1. 新增Xcode專案,專案名稱命名為[CoreData]。
2. 加入CoreData.Framework參考
3. 新增DataModel。
這邊命名為country,副檔名為xcdatamodele。
4. 新增Entity
5. 新增NSManagedObjectsubclass 類別
接著選擇我們剛剛建立的Entity。
按下確定後,會看見相對應的類別檔案備建立起來。這個檔案稍後會被拿來作為轉型(Boxing),讓上層的App可以以物件的方式來操作記憶體裡面的資料。
6.在AppDelegate.h檔案裡面做底下的宣告
#import
@interface AppDelegate : UIResponder {
//增加 Core Data 的成員變數
NSManagedObjectContext *m_managedObjectContext;
NSPersistentStoreCoordinator *m_persistentCoordinator;
NSManagedObjectModel *m_managedObjectModel;
}
@property (strong, nonatomic) UIWindow *window;
//增加Core Data的成員變數property定義
@property (nonatomic, retain) NSManagedObjectContext *m_managedObjectContext;
@property (nonatomic, retain) NSManagedObjectModel *m_managedObjectModel;
@property (nonatomic, retain) NSPersistentStoreCoordinator *m_persistentCoordinator;
//這是用來取得資料庫實體位置,Sqlite實體位置會被放置在這個路徑之下 ~/library/Application Support/iPhone Simulator/版本/Applications/你的App/Documents/
-(NSURL *)applicationDocumentsDirectory;
//傳回這個APP的物件本文管理,用來作物件同步
-(NSManagedObjectContext*)managedObjectContext;
//傳回這個APP中管理資料庫的persistent store coordinator 物件
//Persistent Store Coordinator相當於資料文件管理器,處理底層的對資料文件的讀取與寫入。一般我們無需與它打交道。
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator;
//傳回這個APP中
//Managed Object Model是描述應用程序的資料模型,這個模型包含實體(Entity),特性(Property),讀取請求(Fetch Request )等。
-(NSManagedObjectModel*)managedObjectModel;
@end
7. 在AppDelegate.m檔案裡面做底下的宣告
7.1 synthesize 這三個變數
@synthesize m_managedObjectContext;
@synthesize m_persistentCoordinator;
@synthesize m_managedObjectModel;
7.1 實作applicationDocumentsDirectory方法與其內容
這個方法是要取得Sqlite資料庫的實體路徑位置。
-(NSURL *)applicationDocumentsDirectory{
return [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]lastObject];
}
7.3 實作managedObjectContext方法
這個方法會回傳一個物件NSManagedObjectContext實例,App可以藉由其提供的方法來對資料庫做操作的動作。
-(NSManagedObjectContext *)managedObjectContext{
if(m_managedObjectContext != nil){
return m_managedObjectContext;
}
//在managedObjectContext裡面會對persistentStoreCoordinator發出資料庫操作的請求,所以在此可以看到這邊去建立了一個NSPersistentStoreCoordinator 實例
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if(coordinator != nil){
m_managedObjectContext = [[NSManagedObjectContext alloc] init];
[m_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return m_managedObjectContext;
}
7.4 實作persistentStoreCoordinator方法
這個方法會回傳一個物件NSPersistentStoreCoordinator實例。
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (m_persistentCoordinator != nil){
return m_persistentCoordinator;
}
//指定實際存放資料庫的位置,這邊會呼叫applicationDocumentsDirectory方法取得資料庫的實體路徑
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:@"data.sqlite"];
NSError *error = nil;
//呼叫managedObjectModel,經由managedObjectModel讀取資料模型來生成被管理的物件Managed object
m_persistentCoordinator = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:[self managedObjectModel]];
if(![m_persistentCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]){
NSLog(@"讀取資料時發生錯誤 %@,%@",error,[error userInfo]);
abort();
}
return m_persistentCoordinator;
}
7.4實作managedObjectModel方法
-(NSManagedObjectModel *)managedObjectModel{
if(m_managedObjectModel != nil){
return m_managedObjectModel;
}
//讀取資料模型來生成被管理的物件Managedobject
NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"country" withExtension:@"momd"];
m_managedObjectModel = [[NSManagedObjectModel alloc]initWithContentsOfURL:modelURL];
return m_managedObjectModel;
}
8.在iPhoneStory Board上建立底如下圖的控制項。
在ViewController.h 檔案裡面的程式如下。
#import
@class AppDelegate;
@interface ViewController : UIViewController{
AppDelegate* appDelegate;
NSArray *data;
}
@property (weak, nonatomic) IBOutlet UITextField *mylabel1;
@property (weak, nonatomic) IBOutlet UITextField *mylabel2;
@property (weak, nonatomic) IBOutlet UITableView *mytable;
- (IBAction)mybtn:(id)sender;
- (IBAction)get_btn:(id)sender;
@end
9.在在ViewController.m檔案裡面新增下方程式。
9.1在資料新增事件中,首先呼叫了NSEntityDescription物件的insertNewObjectForEntityForName方法
,這個方法會在記憶體裡面新增一筆資料,接著我們用先前建立的NSManagedObject Country轉型,讓App可以以物件的方式來操作這筆資料。最後在藉由m_managedObjectContext的save方法將資料同步進SQLite。
//寫資料到Sqlite的事件
- (IBAction)mybtn:(id)sender {
NSLog(@"準備寫資料進入資料庫");
Country *country =(Country*)[NSEntityDescription insertNewObjectForEntityForName:@"Country" inManagedObjectContext:[appDelegate managedObjectContext]];
country.pk = self.mylabel1.text;
country.name = self.mylabel2.text;
NSError* error = nil;
if(![appDelegate.m_managedObjectContext save:&error]){
NSLog(@"新增資料遇到錯誤");
}
}
9.2 相對的再取出資料的部分,比較簡單的方式就是用NSFetchRequest物件取得SQLite資料庫的實體集合,
並且這個物件可以直接被回傳為陣列物件,這樣就不用像上一篇研究一樣,我們還要去自己動手腳把Statement轉成陣列物件,在這個步驟倒是省了不少的工。
//讀取資料的事件
- (IBAction)get_btn:(id)sender {
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Country" inManagedObjectContext:appDelegate.managedObjectContext];
[fetch setEntity:entity];
data = [appDelegate.managedObjectContext executeFetchRequest:fetch error:nil];
[self.mytable reloadData];
NSLog(@"讀取資料庫完畢");
}
10.執行程式可以對資料庫做操作了。
11.事情總是有一好沒兩好。
當我們用軟體去開啓由CoreData幫我們建立的資料庫的時候,會看到,所有的資料庫以及資料欄位名稱都被加了一個大寫的Z當做開頭字母,並且還幫我們多建了兩個資料庫,分別為Z_METDATA還有Z_PRIMARYKEY。
主要的資料欄位裡面也多了三個系統幫我們建立的欄位,分別是Z_PK,Z_ENT,Z_OPT。
我嘗試找了很多文章,希望在CoreData中引入沒有加入這些系統資料的SQLite資料庫,然後來結合CoreData的ORM架構對資料庫做操作,不過未果。所以當你的資料庫是預先就填充了一些資料的時候,就必須使用CoreData幫你建出來的資料庫當基底了,人參啊……………只好繼續動些手腳了
範例程式下載:https://github.com/twbenlu/iOSCoredata
參考資訊:
Core Data FAQ
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdFAQ.html
Core Data Tutorial for iOS: Getting Started
http://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started
Using a Pre-Populated SQLite Database with Core Data on iPhone OS 3.0
Any way to pre populate core data?
http://stackoverflow.com/questions/2230354/any-way-to-pre-populate-core-data
[iOS] Core Data 基礎概念
http://cg2010studio.wordpress.com/2012/12/22/ios-core-data-基礎概念/