iOS開發(fā)系列--詳細介紹數(shù)據(jù)存取
概覽
在iOS開發(fā)中數(shù)據(jù)存儲的方式可以歸納為兩類:一類是存儲為文件,另一類是存儲到數(shù)據(jù)庫。例如前面IOS開發(fā)系列—Objective-C之Foundation框架的文章中提到歸檔、plist文件存儲,包括偏好設(shè)置其本質(zhì)都是存儲為文件,只是說歸檔或者plist文件存儲可以選擇保存到沙盒中,而偏好設(shè)置系統(tǒng)已經(jīng)規(guī)定只能保存到沙盒的Library/Preferences目錄。當然,文件存儲并不作為本文的重點內(nèi)容。本文重點還是說數(shù)據(jù)庫存儲,做過數(shù)據(jù)庫開發(fā)的朋友應(yīng)該知道,可以通過SQL直接訪問數(shù)據(jù)庫,也可以通過ORM進行對象關(guān)系映射訪問數(shù)據(jù)庫。這兩種方式恰恰對應(yīng)iOS中SQLite和Core Data的內(nèi)容,在此將重點進行分析:
- SQLite
- Core Data
- FMDB
SQLite
SQLite是目前主流的嵌入式關(guān)系型數(shù)據(jù)庫,其最主要的特點就是輕量級、跨平臺,當前很多嵌入式操作系統(tǒng)都將其作為數(shù)據(jù)庫首選。雖然SQLite是一款輕型數(shù)據(jù)庫,但是其功能也絕不亞于很多大型關(guān)系數(shù)據(jù)庫。學習數(shù)據(jù)庫就要學習其相關(guān)的定義、操作、查詢語言,也就是大家日常說得SQL語句。和其他數(shù)據(jù)庫相比,SQLite中的SQL語法并沒有太大的差別,因此這里對于SQL語句的內(nèi)容不會過多贅述,大家可以參考SQLite中其他SQL相關(guān)的內(nèi)容,這里還是重點講解iOS中如何使用SQLite構(gòu)建應(yīng)用程序。先看一下SQLite數(shù)據(jù)庫的幾個特點:
- 基于C語言開發(fā)的輕型數(shù)據(jù)庫
- 在iOS中需要使用C語言語法進行數(shù)據(jù)庫操作、訪問(無法使用ObjC直接訪問,因為libsqlite3框架基于C語言編寫)
- SQLite中采用的是動態(tài)數(shù)據(jù)類型,即使創(chuàng)建時定義了一種類型,在實際操作時也可以存儲其他類型,但是推薦建庫時使用合適的類型(特別是應(yīng)用需要考慮跨平臺的情況時)
- 建立連接后通常不需要關(guān)閉連接(盡管可以手動關(guān)閉)
要使用SQLite很簡單,如果在Mac OSX上使用可以考慮到SQLite網(wǎng)站下載命令行工具,也可以使用類似于SQLiteManager、Navicat for SQLite等工具。為了方便大家開發(fā)調(diào)試,建議在開發(fā)環(huán)境中安裝上述工具。
在iOS中操作SQLite數(shù)據(jù)庫可以分為以下幾步(注意先在項目中導入libsqlite3框架):
- 打開數(shù)據(jù)庫,利用sqlite3_open()打開數(shù)據(jù)庫會指定一個數(shù)據(jù)庫文件保存路徑,如果文件存在則直接打開,否則創(chuàng)建并打開。打開數(shù)據(jù)庫會得到一個sqlite3類型的對象,后面需要借助這個對象進行其他操作。
- 執(zhí)行SQL語句,執(zhí)行SQL語句又包括有返回值的語句和無返回值語句。
- 對于無返回值的語句(如增加、刪除、修改等)直接通過sqlite3_exec()函數(shù)執(zhí)行;
- 對于有返回值的語句則首先通過sqlite3_prepare_v2()進行sql語句評估(語法檢測),然后通過sqlite3_step()依次取出查詢結(jié)果的每一行數(shù)據(jù),對于每行數(shù)據(jù)都可以通過對應(yīng)的sqlite3_column_類型()方法獲得對應(yīng)列的數(shù)據(jù),如此反復(fù)循環(huán)直到遍歷完成。當然,最后需要釋放句柄。
在整個操作過程中無需管理數(shù)據(jù)庫連接,對于嵌入式SQLite操作是持久連接(盡管可以通過sqlite3_close()關(guān)閉),不需要開發(fā)人員自己釋放連接??v觀整個操作過程,其實與其他平臺的開發(fā)沒有明顯的區(qū)別,較為麻煩的就是數(shù)據(jù)讀取,在iOS平臺中使用C進行數(shù)據(jù)讀取采用了游標的形式,每次只能讀取一行數(shù)據(jù),較為麻煩。因此實際開發(fā)中不妨對這些操作進行封裝:
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 數(shù)據(jù)庫引用,使用它進行數(shù)據(jù)庫操作 @property (nonatomic) sqlite3 *database; #pragma mark - 共有方法 /** * 打開數(shù)據(jù)庫 * * @param dbname 數(shù)據(jù)庫名稱 */ -(void)openDb:(NSString *)dbname; /** * 執(zhí)行無返回值的sql * * @param sql sql語句 */ -(void)executeNonQuery:(NSString *)sql; /** * 執(zhí)行有返回值的sql * * @param sql sql語句 * * @return 查詢結(jié)果 */ -(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{
//取得數(shù)據(jù)庫保存路徑,通常保存沙盒Documents目錄
NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"%@",directory);
NSString *filePath=[directory stringByAppendingPathComponent:dbname];
//如果有數(shù)據(jù)庫則直接打開,否則創(chuàng)建并打開(注意filePath是ObjC中的字符串,需要轉(zhuǎn)化為C語言字符串類型)
if (SQLITE_OK ==sqlite3_open(filePath.UTF8String, &_database)) {
NSLog(@"數(shù)據(jù)庫打開成功!");
}else{
NSLog(@"數(shù)據(jù)庫打開失敗!");
}
}
-(void)executeNonQuery:(NSString *)sql{
char *error;
//單步執(zhí)行sql語句,用于插入、修改、刪除
if (SQLITE_OK!=sqlite3_exec(_database, sql.UTF8String, NULL, NULL,&error)) {
NSLog(@"執(zhí)行SQL語句過程中發(fā)生錯誤!錯誤信息:%s",error);
}
}
-(NSArray *)executeQuery:(NSString *)sql{
NSMutableArray *rows=[NSMutableArray array];//數(shù)據(jù)行
//評估語法正確性
sqlite3_stmt *stmt;
//檢查語法正確性
if (SQLITE_OK==sqlite3_prepare_v2(_database, sql.UTF8String, -1, &stmt, NULL)) {
//單步執(zhí)行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
在上面的類中對于數(shù)據(jù)庫操作進行了封裝,封裝之后數(shù)據(jù)操作更加方便,同時所有的語法都由C轉(zhuǎn)換成了ObjC。
下面仍然以微博查看為例進行SQLite演示。當然實際開發(fā)中微博數(shù)據(jù)是從網(wǎng)絡(luò)讀取的,但是考慮到緩存問題,通常會選擇將微博數(shù)據(jù)保存到本地,下面的Demo演示了將數(shù)據(jù)存放到本地數(shù)據(jù)庫以及數(shù)據(jù)讀取的過程。當然,實際開發(fā)中并不會在視圖控制器中直接調(diào)用數(shù)據(jù)庫操作方法,在這里通常會引入兩個概念Model和Service。Model自不必多說,就是MVC中的模型。而Service指的是操作數(shù)據(jù)庫的服務(wù)層,它封裝了對于Model的基本操作方法,實現(xiàn)具體的業(yè)務(wù)邏輯。為了解耦,在控制器中是不會直接接觸數(shù)據(jù)庫的,控制器中只和模型(模型是領(lǐng)域的抽象)、服務(wù)對象有關(guān)系,借助服務(wù)層對模型進行各類操作,模型的操作反應(yīng)到數(shù)據(jù)庫中就是對表中數(shù)據(jù)的操作。具體關(guān)系如下:
要完成上述功能,首先定義一個應(yīng)用程序全局對象進行數(shù)據(jù)庫、表的創(chuàng)建。為了避免每次都創(chuàng)建數(shù)據(jù)庫和表出錯,這里利用了偏好設(shè)置進行保存當前創(chuàng)建狀態(tài)(其實這也是數(shù)據(jù)存儲的一部分),如果創(chuàng)建過了數(shù)據(jù)庫則不再創(chuàng)建,否則創(chuàng)建數(shù)據(jù)庫和表。
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
其次,定義數(shù)據(jù)模型,這里定義用戶User和微博Status兩個數(shù)據(jù)模型類。注意模型應(yīng)該盡量保持其單純性,僅僅是簡單的POCO,不要引入視圖、控制器等相關(guān)內(nèi)容。
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 - 動態(tài)方法
/**
* 初始化用戶
*
* @param name 用戶名
* @param city 所在城市
*
* @return 用戶對象
*/
-(KCUser *)initWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city;
/**
* 使用字典初始化用戶對象
*
* @param dic 用戶數(shù)據(jù)
*
* @return 用戶對象
*/
-(KCUser *)initWithDictionary:(NSDictionary *)dic;
#pragma mark - 靜態(tài)方法
+(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;//發(fā)送用戶 @property (nonatomic,copy) NSString *createdAt;//創(chuàng)建時間 @property (nonatomic,copy) NSString *source;//設(shè)備來源 @property (nonatomic,copy) NSString *text;//微博內(nèi)容 #pragma mark - 動態(tài)方法 /** * 初始化微博數(shù)據(jù) * * @param createAt 創(chuàng)建日期 * @param source 來源 * @param text 微博內(nèi)容 * @param user 發(fā)送用戶 * * @return 微博對象 */ -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user; /** * 初始化微博數(shù)據(jù) * * @param profileImageUrl 用戶頭像 * @param mbtype 會員類型 * @param createAt 創(chuàng)建日期 * @param source 來源 * @param text 微博內(nèi)容 * @param userId 用戶編號 * * @return 微博對象 */ -(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId; /** * 使用字典初始化微博對象 * * @param dic 字典數(shù)據(jù) * * @return 微博對象 */ -(KCStatus *)initWithDictionary:(NSDictionary *)dic; #pragma mark - 靜態(tài)方法 /** * 初始化微博數(shù)據(jù) * * @param createAt 創(chuàng)建日期 * @param source 來源 * @param text 微博內(nèi)容 * @param user 發(fā)送用戶 * * @return 微博對象 */ +(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user; /** * 初始化微博數(shù)據(jù) * * @param profileImageUrl 用戶頭像 * @param mbtype 會員類型 * @param createAt 創(chuàng)建日期 * @param source 來源 * @param text 微博內(nèi)容 * @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
然后,編寫服務(wù)類,進行數(shù)據(jù)的增、刪、改、查操作,由于服務(wù)類方法同樣不需要過多的配置,因此定義為單例,保證程序中只有一個實例即可。服務(wù)類中調(diào)用前面封裝的數(shù)據(jù)庫方法將對數(shù)據(jù)庫的操作轉(zhuǎn)換為對模型的操作。
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; /** * 根據(jù)用戶名刪除用戶 * * @param name 用戶名 */ -(void)removeUserByName:(NSString *)name; /** * 修改用戶內(nèi)容 * * @param user 用戶對象 */ -(void)modifyUser:(KCUser *)user; /** * 根據(jù)用戶編號取得用戶 * * @param Id 用戶編號 * * @return 用戶對象 */ -(KCUser *)getUserById:(int)Id; /** * 根據(jù)用戶名取得用戶 * * @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; /** * 修改微博內(nèi)容 * * @param status 微博對象 */ -(void)modifyStatus:(KCStatus *)status; /** * 根據(jù)編號取得微博 * * @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
最后,在視圖控制器中調(diào)用相應(yīng)的服務(wù)層進行各類數(shù)據(jù)操作,在下面的代碼中分別演示了增、刪、改、查四類操作。
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了 要求很簡單】真心回饋粉絲,小編覺得現(xiàn)在最好的獎品就是iPhone6了。今起到12月31日,關(guān)注我們,轉(zhuǎn)發(fā)微博,就有機會獲iPhone6(獎品可能需要等待)!每月抽一臺[鼓掌]。不費事,還是試試吧,萬一中了呢" userId:2];
[[KCStatusService sharedKCStatusService] addStatus:status3];
KCStatus *status4=[KCStatus statusWithCreateAt:@"9:45" source:@"iPhone 6" text:@"重大新聞:蒂姆庫克宣布出柜后,ISIS戰(zhàn)士怒扔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客戶端里發(fā)現(xiàn)一個悅單《Infinite 金明洙》,推薦給大家! " userId:1];
[[KCStatusService sharedKCStatusService] addStatus:status6];
KCStatus *status7=[KCStatus statusWithCreateAt:@"11:20" source:@"iPhone 6" text:@"如果sony吧mp3播放器產(chǎn)品發(fā)展下去,不貪圖手頭節(jié)目源的現(xiàn)實利益,就木有蘋果的ipod,也就木有iphone。柯達類似的現(xiàn)實利益,不自我革命的案例也是一種巨頭的宿命。" 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~價格美麗,行貨,全國聯(lián)?!玦Phone6 iPhone6Plus卡西歐TR150 TR200 TR350 TR350S全面到貨 招收各種代理![給力]微信:39017366" userId:3];
[[KCStatusService sharedKCStatusService] addStatus:status9];
KCStatus *status10=[KCStatus statusWithCreateAt:@"13:26" source:@"iPhone 6" text:@"猜到猴哥玩手機時所思所想者,再獎iPhone一部。(獎品由“2014年野生動物攝影師”評委會頒發(fā))" userId:3];
[[KCStatusService sharedKCStatusService] addStatus:status10];
}
-(void)removeUser{
//注意在SQLite中區(qū)分大小寫
[[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 加載數(shù)據(jù)
-(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
項目目錄結(jié)構(gòu):

運行效果:

Core Data
基本概念
當前,各類應(yīng)用開發(fā)中只要牽扯到數(shù)據(jù)庫操作通常都會用到一個概念“對象關(guān)系映射(ORM)”。例如在Java平臺使用Hibernate,在.NET平臺使用Entity Framework、Linq、NHibernate等。在iOS中也不例外,iOS中ORM框架首選Core Data,這是官方推薦的,不需要借助第三方框架。無論是哪種平臺、哪種技術(shù),ORM框架的作用都是相同的,那就是將關(guān)系數(shù)據(jù)庫中的表(準確的說是實體)轉(zhuǎn)換為程序中的對象,其本質(zhì)還是對數(shù)據(jù)庫的操作(例如Core Data中如果存儲類型配置為SQLite則本質(zhì)還是操作的SQLite數(shù)據(jù)庫)。細心的朋友應(yīng)該已經(jīng)注意到,在上面的SQLite中其實我們在KCMainViewController中進行的數(shù)據(jù)庫操作已經(jīng)轉(zhuǎn)換為了對象操作,服務(wù)層中的方法中已經(jīng)將對數(shù)據(jù)庫的操作封裝起來,轉(zhuǎn)換為了對Model的操作,這種方式已經(jīng)是面向?qū)ο蟮摹I鲜鐾ㄟ^將對象映射到實體的過程完全是手動完成的,相對來說操作比較復(fù)雜,就拿對KCStatus對象的操作來說:首先要手動創(chuàng)建數(shù)據(jù)庫(Status表),其次手動創(chuàng)建模型KCStatus,接著創(chuàng)建服務(wù)層KCStatusService。Core Data正是為了解決這個問題而產(chǎn)生的,它將數(shù)據(jù)庫的創(chuàng)建、表的創(chuàng)建、對象和表的轉(zhuǎn)換等操作封裝起來,簡化了我們的操作(注意Core Data只是將對象關(guān)系的映射簡化了,并不是把服務(wù)層替代了,這一點大家需要明白)。
使用Core Data進行數(shù)據(jù)庫存取并不需要手動創(chuàng)建數(shù)據(jù)庫,這個過程完全由Core Data框架完成,開發(fā)人員面對的是模型,主要的工作就是把模型創(chuàng)建起來,具體數(shù)據(jù)庫如何創(chuàng)建則不用管。在iOS項目中添加“Data Model”文件。然后在其中創(chuàng)建實體和關(guān)系:

模型創(chuàng)建的過程中需要注意:
- 實體對象不需要創(chuàng)建ID主鍵,Attributes中應(yīng)該是有意義屬性(創(chuàng)建過程中應(yīng)該考慮對象的屬性而不是數(shù)據(jù)庫中表有幾個字段,盡管多數(shù)屬性會對應(yīng)表的字段)。
- 所有的屬性應(yīng)該指定具體類型(盡管在SQLite中可以不指定),因為實體對象會對應(yīng)生成ObjC模型類。
- 實體對象中其他實體對象類型的屬性應(yīng)該通過Relationships建立,并且注意實體之間的對應(yīng)關(guān)系(例如一個用戶有多條微博,而一條微博則只屬于一個用戶,用戶和微博形成一對多的關(guān)系)。
以上模型創(chuàng)建后,接下來就是根據(jù)上面的模型文件(.xcdatamodeld文件)生成具體的實體類。在Xcode中添加“NSManagedObject Subclass”文件,按照步驟選擇創(chuàng)建的模型及實體,Xcode就會根據(jù)所創(chuàng)建模型生成具體的實體類。
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
很顯然,通過模型生成類的過程相當簡單,通常這些類也不需要手動維護,如果模型發(fā)生的變化只要重新生成即可。有幾點需要注意:
- 所有的實體類型都繼承于NSManagedObject,每個NSManagedObject對象對應(yīng)著數(shù)據(jù)庫中一條記錄。
- 集合屬性(例如User中的status)生成了訪問此屬性的分類方法。
- 使用@dynamic代表具體屬性實現(xiàn),具體實現(xiàn)細節(jié)不需要開發(fā)人員關(guān)心。
當然,了解了這些還不足以完成數(shù)據(jù)的操作。究竟Core Data具體的設(shè)計如何,要完成數(shù)據(jù)的存取我們還需要了解一下Core Data幾個核心的類。
- Persistent Object Store:可以理解為存儲持久對象的數(shù)據(jù)庫(例如SQLite,注意Core Data也支持其他類型的數(shù)據(jù)存儲,例如xml、二進制數(shù)據(jù)等)。
- Managed Object Model:對象模型,對應(yīng)Xcode中創(chuàng)建的模型文件。
- Persistent Store Coordinator:對象模型和實體類之間的轉(zhuǎn)換協(xié)調(diào)器,用于管理不同存儲對象的上下文。
- Managed Object Context:對象管理上下文,負責實體對象和數(shù)據(jù)庫之間的交互。
Core Data使用
Core Data使用起來相對直接使用SQLite3的API而言更加的面向?qū)ο?,操作過程通常分為以下幾個步驟:
1.創(chuàng)建管理上下文
創(chuàng)建管理上下可以細分為:加載模型文件->指定數(shù)據(jù)存儲路徑->創(chuàng)建對應(yīng)數(shù)據(jù)類型的存儲->創(chuàng)建管理對象上下方并指定存儲。
經(jīng)過這幾個步驟之后可以得到管理對象上下文NSManagedObjectContext,以后所有的數(shù)據(jù)操作都由此對象負責。同時如果是第一次創(chuàng)建上下文,Core Data會自動創(chuàng)建存儲文件(例如這里使用SQLite3存儲),并且根據(jù)模型對象創(chuàng)建對應(yīng)的表結(jié)構(gòu)。下圖為第一次運行生成的數(shù)據(jù)庫及相關(guān)映射文件:
為了方便后面使用,NSManagedObjectContext對象可以作為單例或靜態(tài)屬性來保存,下面是創(chuàng)建的管理對象上下文的主要代碼:
-(NSManagedObjectContext *)createDbContext{
NSManagedObjectContext *context;
//打開模型文件,參數(shù)為nil則打開包中所有模型文件并合并成一個
NSManagedObjectModel *model=[NSManagedObjectModel mergedModelFromBundles:nil];
//創(chuàng)建解析器
NSPersistentStoreCoordinator *storeCoordinator=[[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:model];
//創(chuàng)建數(shù)據(jù)庫保存路徑
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(@"數(shù)據(jù)庫打開失??!錯誤:%@",error.localizedDescription);
}else{
context=[[NSManagedObjectContext alloc]init];
context.persistentStoreCoordinator=storeCoordinator;
NSLog(@"數(shù)據(jù)庫打開成功!");
}
return context;
}
2.查詢數(shù)據(jù)
對于有條件的查詢,在Core Data中是通過謂詞來實現(xiàn)的。首先創(chuàng)建一個請求,然后設(shè)置請求條件,最后調(diào)用上下文執(zhí)行請求的方法。
-(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(@"添加過程中發(fā)生錯誤,錯誤信息:%@!",error.localizedDescription);
}
}
如果有多個條件,只要使用謂詞組合即可,那么對于關(guān)聯(lián)對象條件怎么查詢呢?這里分為兩種情況進行介紹:
a.查找一個對象只有唯一一個關(guān)聯(lián)對象的情況,例如查找用戶名為“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語句會發(fā)現(xiàn)其實就是把Status表和User表進行了關(guān)聯(lián)查詢(JOIN連接)。
b.查找一個對象有多個關(guān)聯(lián)對象的情況,例如查找發(fā)送微博內(nèi)容中包含“Watch”并且用戶昵稱為“小娜”的用戶(一個用戶有多條微博),此時可以充分利用謂詞進行過濾。
-(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;
}
注意如果單純查找微博中包含“Watch”的用戶,直接查出對應(yīng)的微博,然后通過每個微博的user屬性即可獲得用戶,此時就不用使用額外的謂詞過濾條件。
3.插入數(shù)據(jù)
插入數(shù)據(jù)需要調(diào)用實體描述對象NSEntityDescription返回一個實體對象,然后設(shè)置對象屬性,最后保存當前上下文即可。這里需要注意,增、刪、改操作完最后必須調(diào)用管理對象上下文的保存方法,否則操作不會執(zhí)行。
-(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(@"添加過程中發(fā)生錯誤,錯誤信息:%@!",error.localizedDescription);
}
}
4.刪除數(shù)據(jù)
刪除數(shù)據(jù)可以直接調(diào)用管理對象上下文的deleteObject方法,刪除完保存上下文即可。注意,刪除數(shù)據(jù)前必須先查詢到對應(yīng)對象。
-(void)removeUser:(User *)user{
[self.context deleteObject:user];
NSError *error;
if (![self.context save:&error]) {
NSLog(@"刪除過程中發(fā)生錯誤,錯誤信息:%@!",error.localizedDescription);
}
}
5.修改數(shù)據(jù)
修改數(shù)據(jù)首先也是取出對應(yīng)的實體對象,然后通過修改對象的屬性,最后保存上下文。
-(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(@"修改過程中發(fā)生錯誤,錯誤信息:%@",error.localizedDescription);
}
}
調(diào)試
雖然Core Data(如果使用SQLite數(shù)據(jù)庫)操作最終轉(zhuǎn)換為SQL操作,但是調(diào)試起來卻不像操作SQL那么方便。特別是對于初學者而言經(jīng)常出現(xiàn)查詢報錯的問題,如果能看到最終生成的SQL語句自然對于調(diào)試很有幫助。事實上在Xcode中是支持Core Data調(diào)試的,具體操作:Product-Scheme-Edit Scheme-Run-Arguments中依次添加兩個參數(shù)(注意參數(shù)順序不能錯):-com.apple.CoreData.SQLDebug、1。然后在運行程序過程中如果操作了數(shù)據(jù)庫就會將SQL語句打印在輸出面板。
注意:如果模型發(fā)生了變化,此時可以重新生成實體類文件,但是所生成的數(shù)據(jù)庫并不會自動更新,這時需要考慮重新生成數(shù)據(jù)庫并遷移原有的數(shù)據(jù)。
FMDB
基本使用
相比于SQLite3來說Core Data存在著諸多優(yōu)勢,它面向?qū)ο螅_發(fā)人員不必過多的關(guān)心更多數(shù)據(jù)庫操作知識,同時它基于ObjC操作,書寫更加優(yōu)雅等。但是它本身也存在著一定的限制,例如如果考慮到跨平臺,則只能選擇SQLite,因為無論是iOS還是Android都可以使用同一個數(shù)據(jù)庫,降低了開發(fā)成本和維護成本。其次是當前多數(shù)ORM框架都存在的性能問題,因為ORM最終轉(zhuǎn)化為SQL操作,其中牽扯到模型數(shù)據(jù)轉(zhuǎn)化,其性能自然比不上直接使用SQL操作數(shù)據(jù)庫。那么有沒有更好的選擇呢?答案就是對SQLite進行封裝。
其實通過前面對于SQLite的分析,大家應(yīng)該已經(jīng)看到KCDbManager就是對于SQLite封裝的結(jié)果,開發(fā)人員面對的只有SQL和ObjC方法,不用過多l(xiāng)ibsqlite3的C語言API。但它畢竟只是一個簡單的封裝,還有更多的細節(jié)沒有考慮,例如如何處理并發(fā)安全性,如何更好的處理事務(wù)等。因此,這里推薦使用第三方框架FMDB,整個框架非常輕量級但又不失靈活性,也是很多企業(yè)開發(fā)的首選。
1.FMDB既然是對于libsqlite3框架的封裝,自然使用起來也是類似的,使用前也要打開一個數(shù)據(jù)庫,這個數(shù)據(jù)庫文件存在則直接打開否則會創(chuàng)建并打開。這里FMDB引入了一個FMDatabase對象來表示數(shù)據(jù)庫,打開數(shù)據(jù)庫和后面的數(shù)據(jù)庫操作全部依賴此對象。下面是打開數(shù)據(jù)庫獲得FMDatabase對象的代碼:
-(void)openDb:(NSString *)dbname{
//取得數(shù)據(jù)庫保存路徑,通常保存沙盒Documents目錄
NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"%@",directory);
NSString *filePath=[directory stringByAppendingPathComponent:dbname];
//創(chuàng)建FMDatabase對象
self.database=[FMDatabase databaseWithPath:filePath];
//打開數(shù)據(jù)上
if ([self.database open]) {
NSLog(@"數(shù)據(jù)庫打開成功!");
}else{
NSLog(@"數(shù)據(jù)庫打開失敗!");
}
}
注意:dataWithPath中的路徑參數(shù)一般會選擇保存到沙箱中的Documents目錄中;如果這個參數(shù)設(shè)置為nil則數(shù)據(jù)庫會在內(nèi)存中創(chuàng)建;如果設(shè)置為@””則會在沙箱中的臨時目錄創(chuàng)建,應(yīng)用程序關(guān)閉則文件刪除。
2.對于數(shù)據(jù)庫的操作跟前面KCDbManager的封裝是類似的,在FMDB中FMDatabase類提供了兩個方法executeUpdate:和executeQuery:分別用于執(zhí)行無返回結(jié)果的查詢和有返回結(jié)果的查詢。當然這兩個方法有很多的重載這里就不詳細解釋了。唯一需要指出的是,如果調(diào)用有格式化參數(shù)的sql語句時,格式化符號使用“?”而不是“%@”、等。下面是兩種情況的代碼片段:
a.無返回結(jié)果
-(void)executeNonQuery:(NSString *)sql{
//執(zhí)行更新sql語句,用于插入、修改、刪除
if (![self.database executeUpdate:sql]) {
NSLog(@"執(zhí)行SQL語句過程中發(fā)生錯誤!");
}
}
b.有返回結(jié)果
-(NSArray *)executeQuery:(NSString *)sql{
NSMutableArray *array=[NSMutableArray array];
//執(zhí)行查詢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;
}
對于有返回結(jié)果的查詢而言,查詢完返回一個游標FMResultSet,通過遍歷游標進行查詢。而且FMDB中提供了大量intForColumn、stringForColumn等方法進行取值。
并發(fā)和事務(wù)
我們知道直接使用libsqlite3進行數(shù)據(jù)庫操作其實是線程不安全的,如果遇到多個線程同時操作一個表的時候可能會發(fā)生意想不到的結(jié)果。為了解決這個問題建議在多線程中使用FMDatabaseQueue對象,相比FMDatabase而言,它是線程安全的。
創(chuàng)建FMDatabaseQueue的方法是類似的,調(diào)用databaseQueueWithPath:方法即可。注意這里不需要調(diào)用打開操作。
-(void)openDb:(NSString *)dbname{
//取得數(shù)據(jù)庫保存路徑,通常保存沙盒Documents目錄
NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSLog(@"%@",directory);
NSString *filePath=[directory stringByAppendingPathComponent:dbname];
//創(chuàng)建FMDatabaseQueue對象
self.database=[FMDatabaseQueue databaseQueueWithPath:filePath];
}
然后所有的增刪改查操作調(diào)用FMDatabaseQueue的inDatabase:方法在block中執(zhí)行操作sql語句即可。
-(void)executeNonQuery:(NSString *)sql{
//執(zhí)行更新sql語句,用于插入、修改、刪除
[self.database inDatabase:^(FMDatabase *db) {
[db executeUpdate:sql];
}];
}
-(NSArray *)executeQuery:(NSString *)sql{
NSMutableArray *array=[NSMutableArray array];
[self.database inDatabase:^(FMDatabase *db) {
//執(zhí)行查詢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;
}
之所以將事務(wù)放到FMDB中去說并不是因為只有FMDB才支持事務(wù),而是因為FMDB將其封裝成了幾個方法來調(diào)用,不用自己寫對應(yīng)的sql而已。其實在在使用libsqlite3操作數(shù)據(jù)庫時也是原生支持事務(wù)的(因為這里的事務(wù)是基于數(shù)據(jù)庫的,F(xiàn)MDB還是使用的SQLite數(shù)據(jù)庫),只要在執(zhí)行sql語句前加上“begin transaction;”執(zhí)行完之后執(zhí)行“commit transaction;”或者“rollback transaction;”進行提交或回滾即可。另外在Core Data中大家也可以發(fā)現(xiàn),所有的增、刪、改操作之后必須調(diào)用上下文的保存方法,其實本身就提供了事務(wù)的支持,只要不調(diào)用保存方法,之前所有的操作是不會提交的。在FMDB中FMDatabase有beginTransaction、commit、rollback三個方法進行開啟事務(wù)、提交事務(wù)和回滾事務(wù)。
原文鏈接:http://www.cnblogs.com/kenshincui/p/4077833.html
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解iOS中position:fixed吸底時的滑動出現(xiàn)抖動的解決方案
這篇文章主要介紹了詳解iOS中position:fixed吸底時的滑動出現(xiàn)抖動的解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
iOS中關(guān)于模塊化開發(fā)解決方案(純干貨)
這篇文章主要介紹了iOS中關(guān)于模塊化開發(fā)解決方案(純干貨)的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09
iOS端React Native差異化增量更新的實現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于iOS端React Native差異化增量更新的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-06-06
怎么防止ios系統(tǒng)被抓包?防止ios系統(tǒng)被抓包的方法
怎么防止ios系統(tǒng)被抓包?下面小編就為大家分享一篇防止ios系統(tǒng)被抓包的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
iOS實現(xiàn)類似格瓦拉電影的轉(zhuǎn)場動畫
這篇文章主要給大家介紹了利用iOS如何實現(xiàn)類似格瓦拉電影的轉(zhuǎn)場動畫,文中給出了詳細步驟實現(xiàn)代碼,對大家的學習和理解很有幫助,有需要的朋友們可以參考借鑒,下面來一起看看吧。2016-11-11

