在iOS開發中數據存儲的方式能夠概括爲兩類:一類是存儲爲文件,另外一類是存儲到數據庫。例如前面IOS開發系列—Objective-C之Foundation框架的文章中提到歸檔、plist文件存儲,包括偏好設置其本質都是存儲爲文件,只是說歸檔或者plist文件存儲能夠選擇保存到沙盒中,而偏好設置系統已經規定只能保存到沙盒的Library/Preferences目錄。固然,文件存儲並不做爲本文的重點內容。本文重點仍是說數據庫存儲,作過數據庫開發的朋友應該知道,能夠經過SQL直接訪問數據庫,也能夠經過ORM進行對象關係映射訪問數據庫。這兩種方式偏偏對應iOS中SQLite和Core Data的內容,在此將重點進行分析: html
SQLite是目前主流的嵌入式關係型數據庫,其最主要的特色就是輕量級、跨平臺,當前不少嵌入式操做系統都將其做爲數據庫首選。雖然SQLite是一款輕型數據庫,可是其功能也毫不亞於不少大型關係數據庫。學習數據庫就要學習其相關的定義、操做、查詢語言,也就是你們平常說得SQL語句。和其餘數據庫相比,SQLite中的SQL語法並無太大的差異,所以這裏對於SQL語句的內容不會過多贅述,你們能夠參考SQLite中其餘SQL相關的內容,這裏仍是重點講解iOS中如何使用SQLite構建應用程序。先看一下SQLite數據庫的幾個特色:sql
要使用SQLite很簡單,若是在Mac OSX上使用能夠考慮到SQLite官方網站下載命令行工具,也可使用相似於SQLiteManager、Navicat for SQLite等工具。爲了方便你們開發調試,建議在開發環境中安裝上述工具。數據庫
在iOS中操做SQLite數據庫能夠分爲如下幾步(注意先在項目中導入libsqlite3框架):緩存
在整個操做過程當中無需管理數據庫鏈接,對於嵌入式SQLite操做是持久鏈接(儘管能夠經過sqlite3_close()關閉),不須要開發人員本身釋放鏈接。縱觀整個操做過程,其實與其餘平臺的開發沒有明顯的區別,較爲麻煩的就是數據讀取,在iOS平臺中使用C進行數據讀取採用了遊標的形式,每次只能讀取一行數據,較爲麻煩。所以實際開發中不妨對這些操做進行封裝:安全
KCDbManager.h微信
// // DbManager.h // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import <sqlite3.h> #import "KCSingleton.h" @interface KCDbManager : NSObject singleton_interface(KCDbManager); #pragma mark - 屬性 #pragma mark 數據庫引用,使用它進行數據庫操做 @property (nonatomic) sqlite3 *database; #pragma mark - 共有方法 /** * 打開數據庫 * * @param dbname 數據庫名稱 */ -(void)openDb:(NSString *)dbname; /** * 執行無返回值的sql * * @param sql sql語句 */ -(void)executeNonQuery:(NSString *)sql; /** * 執行有返回值的sql * * @param sql sql語句 * * @return 查詢結果 */ -(NSArray *)executeQuery:(NSString *)sql; @end
KCDbManager.m網絡
// // DbManager.m // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCDbManager.h" #import <sqlite3.h> #import "KCSingleton.h" #import "KCAppConfig.h" #ifndef kDatabaseName #define kDatabaseName @"myDatabase.db" #endif @interface KCDbManager() @end @implementation KCDbManager singleton_implementation(KCDbManager) #pragma mark 重寫初始化方法 -(instancetype)init{ KCDbManager *manager; if((manager=[super init])) { [manager openDb:kDatabaseName]; } return manager; } -(void)openDb:(NSString *)dbname{ //取得數據庫保存路徑,一般保存沙盒Documents目錄 NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSLog(@"%@",directory); NSString *filePath=[directory stringByAppendingPathComponent:dbname]; //若是有數據庫則直接打開,不然建立並打開(注意filePath是ObjC中的字符串,須要轉化爲C語言字符串類型) if (SQLITE_OK ==sqlite3_open(filePath.UTF8String, &_database)) { NSLog(@"數據庫打開成功!"); }else{ NSLog(@"數據庫打開失敗!"); } } -(void)executeNonQuery:(NSString *)sql{ char *error; //單步執行sql語句,用於插入、修改、刪除 if (SQLITE_OK!=sqlite3_exec(_database, sql.UTF8String, NULL, NULL,&error)) { NSLog(@"執行SQL語句過程當中發生錯誤!錯誤信息:%s",error); } } -(NSArray *)executeQuery:(NSString *)sql{ NSMutableArray *rows=[NSMutableArray array];//數據行 //評估語法正確性 sqlite3_stmt *stmt; //檢查語法正確性 if (SQLITE_OK==sqlite3_prepare_v2(_database, sql.UTF8String, -1, &stmt, NULL)) { //單步執行sql語句 while (SQLITE_ROW==sqlite3_step(stmt)) { int columnCount= sqlite3_column_count(stmt); NSMutableDictionary *dic=[NSMutableDictionary dictionary]; for (int i=0; i<columnCount; i++) { const char *name= sqlite3_column_name(stmt, i);//取得列名 const unsigned char *value= sqlite3_column_text(stmt, i);//取得某列的值 dic[[NSString stringWithUTF8String:name]]=[NSString stringWithUTF8String:(const char *)value]; } [rows addObject:dic]; } } //釋放句柄 sqlite3_finalize(stmt); return rows; } @end
在上面的類中對於數據庫操做進行了封裝,封裝以後數據操做更加方便,同時全部的語法都由C轉換成了ObjC。多線程
下面仍然以微博查看爲例進行SQLite演示。固然實際開發中微博數據是從網絡讀取的,可是考慮到緩存問題,一般會選擇將微博數據保存到本地,下面的Demo演示了將數據存放到本地數據庫以及數據讀取的過程。固然,實際開發中並不會在視圖控制器中直接調用數據庫操做方法,在這裏一般會引入兩個概念Model和Service。Model自沒必要多說,就是MVC中的模型。而Service指的是操做數據庫的服務層,它封裝了對於Model的基本操做方法,實現具體的業務邏輯。爲了解耦,在控制器中是不會直接接觸數據庫的,控制器中只和模型(模型是領域的抽象)、服務對象有關係,藉助服務層對模型進行各種操做,模型的操做反應到數據庫中就是對錶中數據的操做。具體關係以下:併發
要完成上述功能,首先定義一個應用程序全局對象進行數據庫、表的建立。爲了不每次都建立數據庫和表出錯,這裏利用了偏好設置進行保存當前建立狀態(其實這也是數據存儲的一部分),若是建立過了數據庫則再也不建立,不然建立數據庫和表。app
KCDatabaseCreator.m
// // KCDatabaseCreator.m // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCDatabaseCreator.h" #import "KCDbManager.h" @implementation KCDatabaseCreator +(void)initDatabase{ NSString *key=@"IsCreatedDb"; NSUserDefaults *defaults=[[NSUserDefaults alloc]init]; if ([[defaults valueForKey:key] intValue]!=1) { [self createUserTable]; [self createStatusTable]; [defaults setValue:@1 forKey:key]; } } +(void)createUserTable{ NSString *sql=@"CREATE TABLE User (Id integer PRIMARY KEY AUTOINCREMENT,name text,screenName text, profileImageUrl text,mbtype text,city text)"; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } +(void)createStatusTable{ NSString *sql=@"CREATE TABLE Status (Id integer PRIMARY KEY AUTOINCREMENT,source text,createdAt date,\"text\" text,user integer REFERENCES User (Id))"; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } @end
其次,定義數據模型,這裏定義用戶User和微博Status兩個數據模型類。注意模型應該儘可能保持其單純性,僅僅是簡單的POCO,不要引入視圖、控制器等相關內容。
KCUser.h
// // KCUser.h // UrlConnection // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @interface KCUser : NSObject #pragma mark 編號 @property (nonatomic,strong) NSNumber *Id; #pragma mark 用戶名 @property (nonatomic,copy) NSString *name; #pragma mark 用戶暱稱 @property (nonatomic,copy) NSString *screenName; #pragma mark 頭像 @property (nonatomic,copy) NSString *profileImageUrl; #pragma mark 會員類型 @property (nonatomic,copy) NSString *mbtype; #pragma mark 城市 @property (nonatomic,copy) NSString *city; #pragma mark - 動態方法 /** * 初始化用戶 * * @param name 用戶名 * @param city 所在城市 * * @return 用戶對象 */ -(KCUser *)initWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city; /** * 使用字典初始化用戶對象 * * @param dic 用戶數據 * * @return 用戶對象 */ -(KCUser *)initWithDictionary:(NSDictionary *)dic; #pragma mark - 靜態方法 +(KCUser *)userWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city; @end
KCUser.m
// // KCUser.m // UrlConnection // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCUser.h" @implementation KCUser -(KCUser *)initWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{ if (self=[super init]) { self.name=name; self.screenName=screenName; self.profileImageUrl=profileImageUrl; self.mbtype=mbtype; self.city=city; } return self; } -(KCUser *)initWithDictionary:(NSDictionary *)dic{ if (self=[super init]) { [self setValuesForKeysWithDictionary:dic]; } return self; } +(KCUser *)userWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{ KCUser *user=[[KCUser alloc]initWithName:name screenName:screenName profileImageUrl:profileImageUrl mbtype:mbtype city:city]; return user; } @end
KCStatus.h
// // KCStatus.h // UITableView // // Created by Kenshin Cui on 14-3-1. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCUser.h" @interface KCStatus : NSObject #pragma mark - 屬性 @property (nonatomic,strong) NSNumber *Id;//微博id @property (nonatomic,strong) KCUser *user;//發送用戶 @property (nonatomic,copy) NSString *createdAt;//建立時間 @property (nonatomic,copy) NSString *source;//設備來源 @property (nonatomic,copy) NSString *text;//微博內容 #pragma mark - 動態方法 /** * 初始化微博數據 * * @param createAt 建立日期 * @param source 來源 * @param text 微博內容 * @param user 發送用戶 * * @return 微博對象 */ -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user; /** * 初始化微博數據 * * @param profileImageUrl 用戶頭像 * @param mbtype 會員類型 * @param createAt 建立日期 * @param source 來源 * @param text 微博內容 * @param userId 用戶編號 * * @return 微博對象 */ -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId; /** * 使用字典初始化微博對象 * * @param dic 字典數據 * * @return 微博對象 */ -(KCStatus *)initWithDictionary:(NSDictionary *)dic; #pragma mark - 靜態方法 /** * 初始化微博數據 * * @param createAt 建立日期 * @param source 來源 * @param text 微博內容 * @param user 發送用戶 * * @return 微博對象 */ +(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user; /** * 初始化微博數據 * * @param profileImageUrl 用戶頭像 * @param mbtype 會員類型 * @param createAt 建立日期 * @param source 來源 * @param text 微博內容 * @param userId 用戶編號 * * @return 微博對象 */ +(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId; @end
KCStatus.m
// // KCStatus.m // UITableView // // Created by Kenshin Cui on 14-3-1. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCStatus.h" @implementation KCStatus -(KCStatus *)initWithDictionary:(NSDictionary *)dic{ if (self=[super init]) { [self setValuesForKeysWithDictionary:dic]; self.user=[[KCUser alloc]init]; self.user.Id=dic[@"user"]; } return self; } -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user{ if (self=[super init]) { self.createdAt=createAt; self.source=source; self.text=text; self.user=user; } return self; } -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId{ if (self=[super init]) { self.createdAt=createAt; self.source=source; self.text=text; KCUser *user=[[KCUser alloc]init]; user.Id=[NSNumber numberWithInt:userId]; self.user=user; } return self; } -(NSString *)source{ return [NSString stringWithFormat:@"來自 %@",_source]; } +(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user{ KCStatus *status=[[KCStatus alloc]initWithCreateAt:createAt source:source text:text user:user]; return status; } +(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId{ KCStatus *status=[[KCStatus alloc]initWithCreateAt:createAt source:source text:text userId:userId]; return status; } @end
而後,編寫服務類,進行數據的增、刪、改、查操做,因爲服務類方法一樣不須要過多的配置,所以定義爲單例,保證程序中只有一個實例便可。服務類中調用前面封裝的數據庫方法將對數據庫的操做轉換爲對模型的操做。
KCUserService.h
// // KCUserService.h // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCUser.h" #import "KCSingleton.h" @interface KCUserService : NSObject singleton_interface(KCUserService) /** * 添加用戶信息 * * @param user 用戶對象 */ -(void)addUser:(KCUser *)user; /** * 刪除用戶 * * @param user 用戶對象 */ -(void)removeUser:(KCUser *)user; /** * 根據用戶名刪除用戶 * * @param name 用戶名 */ -(void)removeUserByName:(NSString *)name; /** * 修改用戶內容 * * @param user 用戶對象 */ -(void)modifyUser:(KCUser *)user; /** * 根據用戶編號取得用戶 * * @param Id 用戶編號 * * @return 用戶對象 */ -(KCUser *)getUserById:(int)Id; /** * 根據用戶名取得用戶 * * @param name 用戶名 * * @return 用戶對象 */ -(KCUser *)getUserByName:(NSString *)name; @end
KCUserService.m
// // KCUserService.m // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCUserService.h" #import "KCUser.h" #import "KCDbManager.h" @implementation KCUserService singleton_implementation(KCUserService) -(void)addUser:(KCUser *)user{ NSString *sql=[NSString stringWithFormat:@"INSERT INTO User (name,screenName, profileImageUrl,mbtype,city) VALUES('%@','%@','%@','%@','%@')",user.name,user.screenName, user.profileImageUrl,user.mbtype,user.city]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(void)removeUser:(KCUser *)user{ NSString *sql=[NSString stringWithFormat:@"DELETE FROM User WHERE Id='%@'",user.Id]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(void)removeUserByName:(NSString *)name{ NSString *sql=[NSString stringWithFormat:@"DELETE FROM User WHERE name='%@'",name]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(void)modifyUser:(KCUser *)user{ NSString *sql=[NSString stringWithFormat:@"UPDATE User SET name='%@',screenName='%@',profileImageUrl='%@',mbtype='%@',city='%@' WHERE Id='%@'",user.name,user.screenName,user.profileImageUrl,user.mbtype,user.city,user.Id]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(KCUser *)getUserById:(int)Id{ KCUser *user=[[KCUser alloc]init]; NSString *sql=[NSString stringWithFormat:@"SELECT name,screenName,profileImageUrl,mbtype,city FROM User WHERE Id='%i'", Id]; NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql]; if (rows&&rows.count>0) { [user setValuesForKeysWithDictionary:rows[0]]; } return user; } -(KCUser *)getUserByName:(NSString *)name{ KCUser *user=[[KCUser alloc]init]; NSString *sql=[NSString stringWithFormat:@"SELECT Id, name,screenName,profileImageUrl,mbtype,city FROM User WHERE name='%@'", name]; NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql]; if (rows&&rows.count>0) { [user setValuesForKeysWithDictionary:rows[0]]; } return user; } @end
KCStatusService.h
// // KCStatusService.h // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCSingleton.h" @class KCStatus; @interface KCStatusService : NSObject singleton_interface(KCStatusService) /** * 添加微博信息 * * @param status 微博對象 */ -(void)addStatus:(KCStatus *)status; /** * 刪除微博 * * @param status 微博對象 */ -(void)removeStatus:(KCStatus *)status; /** * 修改微博內容 * * @param status 微博對象 */ -(void)modifyStatus:(KCStatus *)status; /** * 根據編號取得微博 * * @param Id 微博編號 * * @return 微博對象 */ -(KCStatus *)getStatusById:(int)Id; /** * 取得全部微博對象 * * @return 全部微博對象 */ -(NSArray *)getAllStatus; @end
KCStatusService.m
// // KCStatusService.m // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCStatusService.h" #import "KCDbManager.h" #import "KCStatus.h" #import "KCUserService.h" #import "KCSingleton.h" @interface KCStatusService(){ } @end @implementation KCStatusService singleton_implementation(KCStatusService) -(void)addStatus:(KCStatus *)status{ NSString *sql=[NSString stringWithFormat:@"INSERT INTO Status (source,createdAt,\"text\" ,user) VALUES('%@','%@','%@','%@')",status.source,status.createdAt,status.text,status.user.Id]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(void)removeStatus:(KCStatus *)status{ NSString *sql=[NSString stringWithFormat:@"DELETE FROM Status WHERE Id='%@'",status.Id]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(void)modifyStatus:(KCStatus *)status{ NSString *sql=[NSString stringWithFormat:@"UPDATE Status SET source='%@',createdAt='%@',\"text\"='%@' ,user='%@' WHERE Id='%@'",status.source,status.createdAt,status.text,status.user, status.Id]; [[KCDbManager sharedKCDbManager] executeNonQuery:sql]; } -(KCStatus *)getStatusById:(int)Id{ KCStatus *status=[[KCStatus alloc]init]; NSString *sql=[NSString stringWithFormat:@"SELECT Id, source,createdAt,\"text\" ,user FROM Status WHERE Id='%i'", Id]; NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql]; if (rows&&rows.count>0) { [status setValuesForKeysWithDictionary:rows[0]]; status.user=[[KCUserService sharedKCUserService] getUserById:[(NSNumber *)rows[0][@"user"] intValue]] ; } return status; } -(NSArray *)getAllStatus{ NSMutableArray *array=[NSMutableArray array]; NSString *sql=@"SELECT Id, source,createdAt,\"text\" ,user FROM Status ORDER BY Id"; NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql]; for (NSDictionary *dic in rows) { KCStatus *status=[self getStatusById:[(NSNumber *)dic[@"Id"] intValue]]; [array addObject:status]; } return array; } @end
最後,在視圖控制器中調用相應的服務層進行各種數據操做,在下面的代碼中分別演示了增、刪、改、查四類操做。
KCMainViewController.m
// // KCMainTableViewController.m // DataAccess // // Created by Kenshin Cui on 14-3-29. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCMainTableViewController.h" #import "KCDbManager.h" #import "KCDatabaseCreator.h" #import "KCUser.h" #import "KCStatus.h" #import "KCUserService.h" #import "KCStatusService.h" #import "KCStatusTableViewCell.h" @interface KCMainTableViewController (){ NSArray *_status; NSMutableArray *_statusCells; } @end @implementation KCMainTableViewController - (void)viewDidLoad { [super viewDidLoad]; [KCDatabaseCreator initDatabase]; // [self addUsers]; // [self removeUser]; // [self modifyUserInfo]; // [self addStatus]; [self loadStatusData]; } -(void)addUsers{ KCUser *user1=[KCUser userWithName:@"Binger" screenName:@"冰兒" profileImageUrl:@"binger.jpg" mbtype:@"mbtype.png" city:@"北京"]; [[KCUserService sharedKCUserService] addUser:user1]; KCUser *user2=[KCUser userWithName:@"Xiaona" screenName:@"小娜" profileImageUrl:@"xiaona.jpg" mbtype:@"mbtype.png" city:@"北京"]; [[KCUserService sharedKCUserService] addUser:user2]; KCUser *user3=[KCUser userWithName:@"Lily" screenName:@"麗麗" profileImageUrl:@"lily.jpg" mbtype:@"mbtype.png" city:@"北京"]; [[KCUserService sharedKCUserService] addUser:user3]; KCUser *user4=[KCUser userWithName:@"Qianmo" screenName:@"阡陌" profileImageUrl:@"qianmo.jpg" mbtype:@"mbtype.png" city:@"北京"]; [[KCUserService sharedKCUserService] addUser:user4]; KCUser *user5=[KCUser userWithName:@"Yanyue" screenName:@"炎月" profileImageUrl:@"yanyue.jpg" mbtype:@"mbtype.png" city:@"北京"]; [[KCUserService sharedKCUserService] addUser:user5]; } -(void)addStatus{ KCStatus *status1=[KCStatus statusWithCreateAt:@"9:00" source:@"iPhone 6" text:@"一隻雪猴在日本邊泡溫泉邊玩iPhone的照片,得到了\"2014年野生動物攝影師\"大賽特等獎。一塊兒來爲猴子配個詞" userId:1]; [[KCStatusService sharedKCStatusService] addStatus:status1]; KCStatus *status2=[KCStatus statusWithCreateAt:@"9:00" source:@"iPhone 6" text:@"一隻雪猴在日本邊泡溫泉邊玩iPhone的照片,得到了\"2014年野生動物攝影師\"大賽特等獎。一塊兒來爲猴子配個詞" userId:1]; [[KCStatusService sharedKCStatusService] addStatus:status2]; KCStatus *status3=[KCStatus statusWithCreateAt:@"9:30" source:@"iPhone 6" text:@"【咱們送iPhone6了 要求很簡單】真心回饋粉絲,小編以爲如今最好的獎品就是iPhone6了。今起到12月31日,關注咱們,轉發微博,就有機會獲iPhone6(獎品可能須要等待)!每個月抽一臺[鼓掌]。不費事,仍是試試吧,萬一中了呢" userId:2]; [[KCStatusService sharedKCStatusService] addStatus:status3]; KCStatus *status4=[KCStatus statusWithCreateAt:@"9:45" source:@"iPhone 6" text:@"重大新聞:蒂姆庫克宣佈出櫃後,ISIS戰士怒扔iPhone,沙特神職人員呼籲人們換回iPhone 4。[via Pan-Arabia Enquirer]" userId:3]; [[KCStatusService sharedKCStatusService] addStatus:status4]; KCStatus *status5=[KCStatus statusWithCreateAt:@"10:05" source:@"iPhone 6" text:@"小夥伴們,有誰知道怎麼往Iphone4S裏倒東西?倒入的東西又該在哪裏找?用了Iphone這麼長時間,還真的不知道怎麼弄!有誰知道啊?謝謝!" userId:4]; [[KCStatusService sharedKCStatusService] addStatus:status5]; KCStatus *status6=[KCStatus statusWithCreateAt:@"10:07" source:@"iPhone 6" text:@"在音悅臺iPhone客戶端裏發現一個悅單《Infinite 金明洙》,推薦給你們! " userId:1]; [[KCStatusService sharedKCStatusService] addStatus:status6]; KCStatus *status7=[KCStatus statusWithCreateAt:@"11:20" source:@"iPhone 6" text:@"若是sony吧mp3播放器產品發展下去,不貪圖手頭節目源的現實利益,就木有蘋果的ipod,也就木有iphone。柯達相似的現實利益,不自我革命的案例也是一種巨頭的宿命。" userId:2]; [[KCStatusService sharedKCStatusService] addStatus:status7]; KCStatus *status8=[KCStatus statusWithCreateAt:@"13:00" source:@"iPhone 6" text:@"【iPhone 7 Plus】新買的iPhone 7 Plus ,如何?夠酷炫麼?" userId:2]; [[KCStatusService sharedKCStatusService] addStatus:status8]; KCStatus *status9=[KCStatus statusWithCreateAt:@"13:24" source:@"iPhone 6" text:@"自拍神器#卡西歐TR500#,tr350S~價格美麗,行貨,全國聯保~iPhone6 iPhone6Plus卡西歐TR150 TR200 TR350 TR350S全面到貨 招收各類代理![給力]微信:39017366" userId:3]; [[KCStatusService sharedKCStatusService] addStatus:status9]; KCStatus *status10=[KCStatus statusWithCreateAt:@"13:26" source:@"iPhone 6" text:@"猜到猴哥玩手機時所思所想者,再獎iPhone一部。(獎品由「2014年野生動物攝影師」評委會頒發)" userId:3]; [[KCStatusService sharedKCStatusService] addStatus:status10]; } -(void)removeUser{ //注意在SQLite中區分大小寫 [[KCUserService sharedKCUserService] removeUserByName:@"Yanyue"]; } -(void)modifyUserInfo{ KCUser *user1= [[KCUserService sharedKCUserService]getUserByName:@"Xiaona"]; user1.city=@"上海"; [[KCUserService sharedKCUserService] modifyUser:user1]; KCUser *user2= [[KCUserService sharedKCUserService]getUserByName:@"Lily"]; user2.city=@"深圳"; [[KCUserService sharedKCUserService] modifyUser:user2]; } #pragma mark 加載數據 -(void)loadStatusData{ _statusCells=[[NSMutableArray alloc]init]; _status=[[KCStatusService sharedKCStatusService]getAllStatus]; [_status enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { KCStatusTableViewCell *cell=[[KCStatusTableViewCell alloc]init]; cell.status=(KCStatus *)obj; [_statusCells addObject:cell]; }]; NSLog(@"%@",[_status lastObject]); } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return _status.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identtityKey=@"myTableViewCellIdentityKey1"; KCStatusTableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey]; if(cell==nil){ cell=[[KCStatusTableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey]; } cell.status=_status[indexPath.row]; return cell; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return ((KCStatusTableViewCell *)_statusCells[indexPath.row]).height; } -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return 20.0f; } @end
項目目錄結構:
運行效果:
當前,各種應用開發中只要牽扯到數據庫操做一般都會用到一個概念「對象關係映射(ORM)」。例如在Java平臺使用Hibernate,在.NET平臺使用Entity Framework、Linq、NHibernate等。在iOS中也不例外,iOS中ORM框架首選Core Data,這是官方推薦的,不須要藉助第三方框架。不管是哪一種平臺、哪一種技術,ORM框架的做用都是相同的,那就是將關係數據庫中的表(準確的說是實體)轉換爲程序中的對象,其本質仍是對數據庫的操做(例如Core Data中若是存儲類型配置爲SQLite則本質仍是操做的SQLite數據庫)。細心的朋友應該已經注意到,在上面的SQLite中其實咱們在KCMainViewController中進行的數據庫操做已經轉換爲了對象操做,服務層中的方法中已經將對數據庫的操做封裝起來,轉換爲了對Model的操做,這種方式已是面向對象的。上述經過將對象映射到實體的過程徹底是手動完成的,相對來講操做比較複雜,就拿對KCStatus對象的操做來講:首先要手動建立數據庫(Status表),其次手動建立模型KCStatus,接着建立服務層KCStatusService。Core Data正是爲了解決這個問題而產生的,它將數據庫的建立、表的建立、對象和表的轉換等操做封裝起來,簡化了咱們的操做(注意Core Data只是將對象關係的映射簡化了,並非把服務層替代了,這一點你們須要明白)。
使用Core Data進行數據庫存取並不須要手動建立數據庫,這個過程徹底由Core Data框架完成,開發人員面對的是模型,主要的工做就是把模型建立起來,具體數據庫如何建立則不用管。在iOS項目中添加「Data Model」文件。而後在其中建立實體和關係:
模型建立的過程當中須要注意:
以上模型建立後,接下來就是根據上面的模型文件(.xcdatamodeld文件)生成具體的實體類。在Xcode中添加「NSManagedObject Subclass」文件,按照步驟選擇建立的模型及實體,Xcode就會根據所建立模型生成具體的實體類。
User.h
// // User.h // CoreData // // Created by Kenshin Cui on 14/03/27. // Copyright (c) 2014年 cmjstudio. All rights reserved. // #import <Foundation/Foundation.h> #import <CoreData/CoreData.h> @class Status; @interface User : NSManagedObject @property (nonatomic, retain) NSString * city; @property (nonatomic, retain) NSString * mbtype; @property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString * profileImageUrl; @property (nonatomic, retain) NSString * screenName; @property (nonatomic, retain) NSSet *statuses; @end @interface User (CoreDataGeneratedAccessors) - (void)addStatusesObject:(Status *)value; - (void)removeStatusesObject:(Status *)value; - (void)addStatuses:(NSSet *)values; - (void)removeStatuses:(NSSet *)values; @end
User.m
// // User.m // CoreData // // Created by Kenshin Cui on 14/03/27. // Copyright (c) 2014年 cmjstudio. All rights reserved. // #import "User.h" #import "Status.h" @implementation User @dynamic city; @dynamic mbtype; @dynamic name; @dynamic profileImageUrl; @dynamic screenName; @dynamic statuses; @end
Status.h
// // Status.h // CoreData // // Created by Kenshin Cui on 14/03/27. // Copyright (c) 2014年 cmjstudio. All rights reserved. // #import <Foundation/Foundation.h> #import <CoreData/CoreData.h> @interface Status : NSManagedObject @property (nonatomic, retain) NSDate * createdAt; @property (nonatomic, retain) NSString * source; @property (nonatomic, retain) NSString * text; @property (nonatomic, retain) NSManagedObject *user; @end
Status.m
// // Status.m // CoreData // // Created by Kenshin Cui on 14/03/27. // Copyright (c) 2014年 cmjstudio. All rights reserved. // #import "Status.h" @implementation Status @dynamic createdAt; @dynamic source; @dynamic text; @dynamic user; @end
很顯然,經過模型生成類的過程至關簡單,一般這些類也不須要手動維護,若是模型發生的變化只要從新生成便可。有幾點須要注意:
固然,瞭解了這些還不足以完成數據的操做。究竟Core Data具體的設計如何,要完成數據的存取咱們還須要瞭解一下Core Data幾個核心的類。
Core Data使用起來相對直接使用SQLite3的API而言更加的面向對象,操做過程一般分爲如下幾個步驟:
1.建立管理上下文
建立管理上下能夠細分爲:加載模型文件->指定數據存儲路徑->建立對應數據類型的存儲->建立管理對象上下方並指定存儲。
通過這幾個步驟以後能夠獲得管理對象上下文NSManagedObjectContext,之後全部的數據操做都由此對象負責。同時若是是第一次建立上下文,Core Data會自動建立存儲文件(例如這裏使用SQLite3存儲),而且根據模型對象建立對應的表結構。下圖爲第一次運行生成的數據庫及相關映射文件:
爲了方便後面使用,NSManagedObjectContext對象能夠做爲單例或靜態屬性來保存,下面是建立的管理對象上下文的主要代碼:
-(NSManagedObjectContext *)createDbContext{ NSManagedObjectContext *context; //打開模型文件,參數爲nil則打開包中全部模型文件併合併成一個 NSManagedObjectModel *model=[NSManagedObjectModel mergedModelFromBundles:nil]; //建立解析器 NSPersistentStoreCoordinator *storeCoordinator=[[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:model]; //建立數據庫保存路徑 NSString *dir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSLog(@"%@",dir); NSString *path=[dir stringByAppendingPathComponent:@"myDatabase.db"]; NSURL *url=[NSURL fileURLWithPath:path]; //添加SQLite持久存儲到解析器 NSError *error; [storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]; if(error){ NSLog(@"數據庫打開失敗!錯誤:%@",error.localizedDescription); }else{ context=[[NSManagedObjectContext alloc]init]; context.persistentStoreCoordinator=storeCoordinator; NSLog(@"數據庫打開成功!"); } return context; }
2.查詢數據
對於有條件的查詢,在Core Data中是經過謂詞來實現的。首先建立一個請求,而後設置請求條件,最後調用上下文執行請求的方法。
-(void)addUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{ //添加一個對象 User *us= [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.context]; us.name=name; us.screenName=screenName; us.profileImageUrl=profileImageUrl; us.mbtype=mbtype; us.city=city; NSError *error; //保存上下文 if (![self.context save:&error]) { NSLog(@"添加過程當中發生錯誤,錯誤信息:%@!",error.localizedDescription); } }
若是有多個條件,只要使用謂詞組合便可,那麼對於關聯對象條件怎麼查詢呢?這裏分爲兩種狀況進行介紹:
a.查找一個對象只有惟一一個關聯對象的狀況,例如查找用戶名爲「Binger」的微博(一個微博只能屬於一個用戶),經過keypath查詢
-(NSArray *)getStatusesByUserName:(NSString *)name{ NSFetchRequest *request=[NSFetchRequest fetchRequestWithEntityName:@"Status"]; request.predicate=[NSPredicate predicateWithFormat:@"user.name=%@",name]; NSArray *array=[self.context executeFetchRequest:request error:nil]; return array; }
此時若是跟蹤Core Data生成的SQL語句會發現其實就是把Status表和User表進行了關聯查詢(JOIN鏈接)。
b.查找一個對象有多個關聯對象的狀況,例如查找發送微博內容中包含「Watch」而且用戶暱稱爲「小娜」的用戶(一個用戶有多條微博),此時能夠充分利用謂詞進行過濾。
-(NSArray *)getUsersByStatusText:(NSString *)text screenName:(NSString *)screenName{ NSFetchRequest *request=[NSFetchRequest fetchRequestWithEntityName:@"Status"]; request.predicate=[NSPredicate predicateWithFormat:@"text LIKE '*Watch*'",text]; NSArray *statuses=[self.context executeFetchRequest:request error:nil]; NSPredicate *userPredicate= [NSPredicate predicateWithFormat:@"user.screenName=%@",screenName]; NSArray *users= [statuses filteredArrayUsingPredicate:userPredicate]; return users; }
注意若是單純查找微博中包含「Watch」的用戶,直接查出對應的微博,而後經過每一個微博的user屬性便可得到用戶,此時就不用使用額外的謂詞過濾條件。
3.插入數據
插入數據須要調用實體描述對象NSEntityDescription返回一個實體對象,而後設置對象屬性,最後保存當前上下文便可。這裏須要注意,增、刪、改操做完最後必須調用管理對象上下文的保存方法,不然操做不會執行。
-(void)addUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{ //添加一個對象 User *us= [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.context]; us.name=name; us.screenName=screenName; us.profileImageUrl=profileImageUrl; us.mbtype=mbtype; us.city=city; NSError *error; //保存上下文 if (![self.context save:&error]) { NSLog(@"添加過程當中發生錯誤,錯誤信息:%@!",error.localizedDescription); } }
4.刪除數據
刪除數據能夠直接調用管理對象上下文的deleteObject方法,刪除完保存上下文便可。注意,刪除數據前必須先查詢到對應對象。
-(void)removeUser:(User *)user{ [self.context deleteObject:user]; NSError *error; if (![self.context save:&error]) { NSLog(@"刪除過程當中發生錯誤,錯誤信息:%@!",error.localizedDescription); } }
5.修改數據
修改數據首先也是取出對應的實體對象,而後經過修改對象的屬性,最後保存上下文。
-(void)modifyUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{ User *us=[self getUserByName:name]; us.screenName=screenName; us.profileImageUrl=profileImageUrl; us.mbtype=mbtype; us.city=city; NSError *error; if (![self.context save:&error]) { NSLog(@"修改過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); } }
雖然Core Data(若是使用SQLite數據庫)操做最終轉換爲SQL操做,可是調試起來卻不像操做SQL那麼方便。特別是對於初學者而言常常出現查詢報錯的問題,若是能看到最終生成的SQL語句天然對於調試頗有幫助。事實上在Xcode中是支持Core Data調試的,具體操做:Product-Scheme-Edit Scheme-Run-Arguments中依次添加兩個參數(注意參數順序不能錯):-com.apple.CoreData.SQLDebug、1。而後在運行程序過程當中若是操做了數據庫就會將SQL語句打印在輸出面板。
注意:若是模型發生了變化,此時能夠從新生成實體類文件,可是所生成的數據庫並不會自動更新,這時須要考慮從新生成數據庫並遷移原有的數據。
相比於SQLite3來講Core Data存在着諸多優點,它面向對象,開發人員沒必要過多的關心更多數據庫操做知識,同時它基於ObjC操做,書寫更加優雅等。可是它自己也存在着必定的限制,例如若是考慮到跨平臺,則只能選擇SQLite,由於不管是iOS仍是Android均可以使用同一個數據庫,下降了開發成本和維護成本。其次是當前多數ORM框架都存在的性能問題,由於ORM最終轉化爲SQL操做,其中牽扯到模型數據轉化,其性能天然比不上直接使用SQL操做數據庫。那麼有沒有更好的選擇呢?答案就是對SQLite進行封裝。
其實經過前面對於SQLite的分析,你們應該已經看到KCDbManager就是對於SQLite封裝的結果,開發人員面對的只有SQL和ObjC方法,不用過多libsqlite3的C語言API。但它畢竟只是一個簡單的封裝,還有更多的細節沒有考慮,例如如何處理併發安全性,如何更好的處理事務等。所以,這裏推薦使用第三方框架FMDB,整個框架很是輕量級但又不失靈活性,也是不少企業開發的首選。
1.FMDB既然是對於libsqlite3框架的封裝,天然使用起來也是相似的,使用前也要打開一個數據庫,這個數據庫文件存在則直接打開不然會建立並打開。這裏FMDB引入了一個MFDatabase對象來表示數據庫,打開數據庫和後面的數據庫操做所有依賴此對象。下面是打開數據庫得到MFDatabase對象的代碼:
-(void)openDb:(NSString *)dbname{ //取得數據庫保存路徑,一般保存沙盒Documents目錄 NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSLog(@"%@",directory); NSString *filePath=[directory stringByAppendingPathComponent:dbname]; //建立FMDatabase對象 self.database=[FMDatabase databaseWithPath:filePath]; //打開數據上 if ([self.database open]) { NSLog(@"數據庫打開成功!"); }else{ NSLog(@"數據庫打開失敗!"); } }
注意:dataWithPath中的路徑參數通常會選擇保存到沙箱中的Documents目錄中;若是這個參數設置爲nil則數據庫會在內存中建立;若是設置爲@」」則會在沙箱中的臨時目錄建立,應用程序關閉則文件刪除。
2.對於數據庫的操做跟前面KCDbManager的封裝是相似的,在FMDB中FMDatabase類提供了兩個方法executeUpdate:和executeQuery:分別用於執行無返回結果的查詢和有返回結果的查詢。固然這兩個方法有不少的重載這裏就不詳細解釋了。惟一須要指出的是,若是調用有格式化參數的sql語句時,格式化符號使用「?」而不是「%@」、等。下面是兩種狀況的代碼片斷:
a.無返回結果
-(void)executeNonQuery:(NSString *)sql{ //執行更新sql語句,用於插入、修改、刪除 if (![self.database executeUpdate:sql]) { NSLog(@"執行SQL語句過程當中發生錯誤!"); } }
b.有返回結果
-(NSArray *)executeQuery:(NSString *)sql{ NSMutableArray *array=[NSMutableArray array]; //執行查詢sql語句 FMResultSet *result= [self.database executeQuery:sql]; while (result.next) { NSMutableDictionary *dic=[NSMutableDictionary dictionary]; for (int i=0; i<result.columnCount; ++i) { dic[[result columnNameForIndex:i]]=[result stringForColumnIndex:i]; } [array addObject:dic]; } return array; }
對於有返回結果的查詢而言,查詢完返回一個遊標FMResultSet,經過遍歷遊標進行查詢。並且FMDB中提供了大量intForColumn、stringForColumn等方法進行取值。
咱們知道直接使用libsqlite3進行數據庫操做實際上是線程不安全的,若是遇到多個線程同時操做一個表的時候可能會發生意想不到的結果。爲了解決這個問題建議在多線程中使用FMDatabaseQueue對象,相比FMDatabase而言,它是線程安全的。
建立FMDatabaseQueue的方法是相似的,調用databaseQueueWithPath:方法便可。注意這裏不須要調用打開操做。
-(void)openDb:(NSString *)dbname{ //取得數據庫保存路徑,一般保存沙盒Documents目錄 NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSLog(@"%@",directory); NSString *filePath=[directory stringByAppendingPathComponent:dbname]; //建立FMDatabaseQueue對象 self.database=[FMDatabaseQueue databaseQueueWithPath:filePath]; }
而後全部的增刪改查操做調用FMDatabaseQueue的inDatabase:方法在block中執行操做sql語句便可。
-(void)executeNonQuery:(NSString *)sql{ //執行更新sql語句,用於插入、修改、刪除 [self.database inDatabase:^(FMDatabase *db) { [db executeQuery:sql]; }]; } -(NSArray *)executeQuery:(NSString *)sql{ NSMutableArray *array=[NSMutableArray array]; [self.database inDatabase:^(FMDatabase *db) { //執行查詢sql語句 FMResultSet *result= [db executeQuery:sql]; while (result.next) { NSMutableDictionary *dic=[NSMutableDictionary dictionary]; for (int i=0; i<result.columnCount; ++i) { dic[[result columnNameForIndex:i]]=[result stringForColumnIndex:i]; } [array addObject:dic]; } }]; return array; }
之因此將事務放到FMDB中去說並非由於只有FMDB才支持事務,而是由於FMDB將其封裝成了幾個方法來調用,不用本身寫對應的sql而已。其實在在使用libsqlite3操做數據庫時也是原生支持事務的(由於這裏的事務是基於數據庫的,FMDB仍是使用的SQLite數據庫),只要在執行sql語句前加上「begin transaction;」執行完以後執行「commit transaction;」或者「rollback transaction;」進行提交或回滾便可。另外在Core Data中你們也能夠發現,全部的增、刪、改操做以後必須調用上下文的保存方法,其實自己就提供了事務的支持,只要不調用保存方法,以前全部的操做是不會提交的。在FMDB中FMDatabase有beginTransaction、commit、rollback三個方法進行開啓事務、提交事務和回滾事務。