亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

iOS開發系列--詳細介紹數據存取

2020-07-26 03:05:19
字體:
來源:轉載
供稿:網友

概覽

在iOS開發中數據存儲的方式可以歸納為兩類:一類是存儲為文件,另一類是存儲到數據庫。例如前面IOS開發系列―Objective-C之Foundation框架的文章中提到歸檔、plist文件存儲,包括偏好設置其本質都是存儲為文件,只是說歸檔或者plist文件存儲可以選擇保存到沙盒中,而偏好設置系統已經規定只能保存到沙盒的Library/Preferences目錄。當然,文件存儲并不作為本文的重點內容。本文重點還是說數據庫存儲,做過數據庫開發的朋友應該知道,可以通過SQL直接訪問數據庫,也可以通過ORM進行對象關系映射訪問數據庫。這兩種方式恰恰對應iOS中SQLite和Core Data的內容,在此將重點進行分析:

  • SQLite
  • Core Data
  • FMDB

SQLite

SQLite是目前主流的嵌入式關系型數據庫,其最主要的特點就是輕量級、跨平臺,當前很多嵌入式操作系統都將其作為數據庫首選。雖然SQLite是一款輕型數據庫,但是其功能也絕不亞于很多大型關系數據庫。學習數據庫就要學習其相關的定義、操作、查詢語言,也就是大家日常說得SQL語句。和其他數據庫相比,SQLite中的SQL語法并沒有太大的差別,因此這里對于SQL語句的內容不會過多贅述,大家可以參考SQLite中其他SQL相關的內容,這里還是重點講解iOS中如何使用SQLite構建應用程序。先看一下SQLite數據庫的幾個特點:

  • 基于C語言開發的輕型數據庫
  • 在iOS中需要使用C語言語法進行數據庫操作、訪問(無法使用ObjC直接訪問,因為libsqlite3框架基于C語言編寫)
  • SQLite中采用的是動態數據類型,即使創建時定義了一種類型,在實際操作時也可以存儲其他類型,但是推薦建庫時使用合適的類型(特別是應用需要考慮跨平臺的情況時)
  • 建立連接后通常不需要關閉連接(盡管可以手動關閉)

要使用SQLite很簡單,如果在Mac OSX上使用可以考慮到SQLite網站下載命令行工具,也可以使用類似于SQLiteManager、Navicat for SQLite等工具。為了方便大家開發調試,建議在開發環境中安裝上述工具。

在iOS中操作SQLite數據庫可以分為以下幾步(注意先在項目中導入libsqlite3框架):

  1. 打開數據庫,利用sqlite3_open()打開數據庫會指定一個數據庫文件保存路徑,如果文件存在則直接打開,否則創建并打開。打開數據庫會得到一個sqlite3類型的對象,后面需要借助這個對象進行其他操作。
  2. 執行SQL語句,執行SQL語句又包括有返回值的語句和無返回值語句。
  3. 對于無返回值的語句(如增加、刪除、修改等)直接通過sqlite3_exec()函數執行;
  4. 對于有返回值的語句則首先通過sqlite3_prepare_v2()進行sql語句評估(語法檢測),然后通過sqlite3_step()依次取出查詢結果的每一行數據,對于每行數據都可以通過對應的sqlite3_column_類型()方法獲得對應列的數據,如此反復循環直到遍歷完成。當然,最后需要釋放句柄。

在整個操作過程中無需管理數據庫連接,對于嵌入式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 : NSObjectsingleton_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 KCDbManagersingleton_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的基本操作方法,實現具體的業務邏輯。為了解耦,在控制器中是不會直接接觸數據庫的,控制器中只和模型(模型是領域的抽象)、服務對象有關系,借助服務層對模型進行各類操作,模型的操作反應到數據庫中就是對表中數據的操作。具體關系如下:
要完成上述功能,首先定義一個應用程序全局對象進行數據庫、表的創建。為了避免每次都創建數據庫和表出錯,這里利用了偏好設置進行保存當前創建狀態(其實這也是數據存儲的一部分),如果創建過了數據庫則不再創建,否則創建數據庫和表。

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;@endKCUser.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 : NSObjectsingleton_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 KCUserServicesingleton_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 : NSObjectsingleton_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 KCStatusServicesingleton_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~價格美麗,行貨,全國聯?!玦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年野生動物攝影師”評委會頒發)" 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

項目目錄結構:

運行效果:

Core Data

基本概念

當前,各類應用開發中只要牽扯到數據庫操作通常都會用到一個概念“對象關系映射(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”文件。然后在其中創建實體和關系:

模型創建的過程中需要注意:

  • 實體對象不需要創建ID主鍵,Attributes中應該是有意義屬性(創建過程中應該考慮對象的屬性而不是數據庫中表有幾個字段,盡管多數屬性會對應表的字段)。
  • 所有的屬性應該指定具體類型(盡管在SQLite中可以不指定),因為實體對象會對應生成ObjC模型類。
  • 實體對象中其他實體對象類型的屬性應該通過Relationships建立,并且注意實體之間的對應關系(例如一個用戶有多條微博,而一條微博則只屬于一個用戶,用戶和微博形成一對多的關系)。

以上模型創建后,接下來就是根據上面的模型文件(.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

很顯然,通過模型生成類的過程相當簡單,通常這些類也不需要手動維護,如果模型發生的變化只要重新生成即可。有幾點需要注意:

  • 所有的實體類型都繼承于NSManagedObject,每個NSManagedObject對象對應著數據庫中一條記錄。
  • 集合屬性(例如User中的status)生成了訪問此屬性的分類方法。
  • 使用@dynamic代表具體屬性實現,具體實現細節不需要開發人員關心。

當然,了解了這些還不足以完成數據的操作。究竟Core Data具體的設計如何,要完成數據的存取我們還需要了解一下Core Data幾個核心的類。

  • Persistent Object Store:可以理解為存儲持久對象的數據庫(例如SQLite,注意Core Data也支持其他類型的數據存儲,例如xml、二進制數據等)。
  • Managed Object Model:對象模型,對應Xcode中創建的模型文件。
  • Persistent Store Coordinator:對象模型和實體類之間的轉換協調器,用于管理不同存儲對象的上下文。
  • Managed Object Context:對象管理上下文,負責實體對象和數據庫之間的交互。

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 *)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”的用戶,直接查出對應的微博,然后通過每個微博的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語句打印在輸出面板。
注意:如果模型發生了變化,此時可以重新生成實體類文件,但是所生成的數據庫并不會自動更新,這時需要考慮重新生成數據庫并遷移原有的數據。

FMDB

基本使用

相比于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引入了一個FMDatabase對象來表示數據庫,打開數據庫和后面的數據庫操作全部依賴此對象。下面是打開數據庫獲得FMDatabase對象的代碼:

-(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 executeUpdate: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三個方法進行開啟事務、提交事務和回滾事務。

原文鏈接:http://www.cnblogs.com/kenshincui/p/4077833.html

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品调教chinesegay| 91免费在线视频| yw.139尤物在线精品视频| 国产精品视频公开费视频| 亚洲国产毛片完整版| 日韩免费av一区二区| 欧美亚洲第一区| 亚洲欧美国产精品久久久久久久| 中文字幕视频在线免费欧美日韩综合在线看| 亚洲天堂网在线观看| 亚洲热线99精品视频| 久久久久久国产精品久久| 2019中文字幕在线免费观看| 精品福利一区二区| 精品成人乱色一区二区| 亚洲人成电影网站色xx| 日韩欧美高清在线视频| 久久久久九九九九| 日韩欧美亚洲国产一区| 欧美激情精品久久久久久蜜臀| 亚洲第一色中文字幕| 欧美乱妇40p| 91成人福利在线| 国产成人aa精品一区在线播放| 成人做爰www免费看视频网站| 精品国偷自产在线视频99| 成人信息集中地欧美| 亚洲国模精品私拍| 狠狠做深爱婷婷久久综合一区| 国产精品自拍偷拍视频| 欧美成人第一页| 91大神在线播放精品| 中文字幕在线看视频国产欧美| 日韩免费在线观看视频| 久久国产精品99国产精| 97色在线播放视频| 亚洲图片在区色| 成人av在线天堂| 95av在线视频| 国产一区视频在线播放| 91亚洲午夜在线| 国产精品黄色影片导航在线观看| 欧美亚洲另类在线| 国产91ⅴ在线精品免费观看| 国产精品 欧美在线| 国产精品爽爽爽爽爽爽在线观看| 成人福利网站在线观看11| 日韩免费观看高清| 91日韩在线视频| 国产精品高潮呻吟久久av黑人| 久久偷看各类女兵18女厕嘘嘘| 福利二区91精品bt7086| 国产香蕉精品视频一区二区三区| 国产精品爽黄69天堂a| 日韩中文字幕欧美| 国产视频在线一区二区| 亚洲91精品在线| 一区二区三区回区在观看免费视频| 日韩av成人在线观看| 一本色道久久综合亚洲精品小说| 欧美床上激情在线观看| 国产精品精品视频一区二区三区| 欧美日韩视频免费播放| 一本色道久久88综合日韩精品| 亚洲精品国产精品乱码不99按摩| 亚洲天堂av综合网| 九色成人免费视频| 午夜精品三级视频福利| 欧美黑人狂野猛交老妇| 91成人在线视频| 欧美天堂在线观看| 国产精品视频专区| 欧美成人免费网| 国产在线观看精品| 国产精品国语对白| 久久久精品久久| 亚洲日韩中文字幕在线播放| 国产精品日韩精品| 欧美激情国内偷拍| 日韩精品久久久久久福利| 91精品国产高清久久久久久久久| 久久九九全国免费精品观看| 亚洲高清免费观看高清完整版| 日韩经典一区二区三区| 日韩欧美在线播放| 日韩精品视频在线观看网址| 久久久久久91| 欧洲成人午夜免费大片| 亚洲18私人小影院| 亚洲成人精品视频| 性欧美xxxx视频在线观看| 欧美日韩国产一区中文午夜| 日韩免费中文字幕| 久久久精品美女| 欧美视频精品一区| 亚洲男人av电影| 国产精品高潮呻吟视频| 77777亚洲午夜久久多人| 久久影视免费观看| 69视频在线免费观看| 性色av一区二区咪爱| 久久国内精品一国内精品| 日韩中文字在线| 久久久国产视频91| 欧美日本国产在线| 精品久久久香蕉免费精品视频| 国产精品一区二区久久国产| 亚洲欧美激情在线视频| 欧美理论片在线观看| 国产精品久久久久久久久久三级| 日韩精品在线观看一区| 最近2019中文字幕一页二页| 国产成人精品免高潮在线观看| 欧美性猛交丰臀xxxxx网站| 亚洲一区免费网站| 日韩电影中文字幕在线观看| 精品久久香蕉国产线看观看gif| 国产999精品视频| 97在线日本国产| 欧美日韩性生活视频| 日本91av在线播放| 欧美另类老肥妇| 97国产真实伦对白精彩视频8| 国产精品久久77777| 91视频国产高清| 亚洲最大的av网站| 国产精品精品一区二区三区午夜版| 久久亚洲国产精品成人av秋霞| 色视频www在线播放国产成人| 亚洲欧洲美洲在线综合| 欧美激情综合亚洲一二区| 5566成人精品视频免费| 亚洲999一在线观看www| 国产精品网红福利| 亚洲高清福利视频| 国产一区二区日韩精品欧美精品| 国产一区二区视频在线观看| 激情成人中文字幕| 久久免费视频网| 成人免费视频在线观看超级碰| 最近2019中文字幕第三页视频| 欧美性受xxxx白人性爽| 久久福利网址导航| 中日韩午夜理伦电影免费| 亚洲人成人99网站| 国产欧美中文字幕| 亚洲最大的免费| 国产精品igao视频| 黄色精品在线看| 久久精品视频中文字幕| 亚洲性av在线| 久久久久亚洲精品成人网小说| 丁香五六月婷婷久久激情| 欧美高清视频免费观看| 亚洲网址你懂得| 国产精品美女www| 亚洲最大av在线| 国产精品免费小视频| 97婷婷大伊香蕉精品视频| 亚洲精品视频久久| 亚洲精美色品网站| 91亚洲国产精品| 久久免费在线观看| 国产精品爱啪在线线免费观看|