SQLiteのライブラリはC言語のライブラリとして提供されています。したがって、SQLiteの各関数の呼び出しや引数の指定方法、パラメータの型はC言語の文法・型に従う必要があります。
特にC言語への型変換に関して、DBの場合は数値(整数、浮動小数点数)、文字列、バイナリ、日付等様々な型があり、Cocoa TouchからC言語への型変換をSQL実行時と結果取得時に行う必要があるため、結構面倒だったりします。
そんなこともあってか、iOSのSQLiteラッパーライブラリとしてFMDBというライブラリがあり、このライブラリはこの面倒な型変換を全て受け持ってくれるため、この点だけでもFMDBを使う魅力は大いにあると思います。(注:後述しますが、FMDBは型変換だけでは無いです)
また、FMDBはARC有効/無効どちらにも対応しています。プリプロセッサでARCの有効状態を判定しているみたいですね。
素でいくかFMDBを利用するかの方針決定
私は素でSQLiteを使う方法とFMDBを使う方法を試しましたが、やはりFMDBを使ったほうがコードが簡単になりますし、なによりもCocoa Touchのクラスを直接使うことが可能である点とマルチスレッド用のクラスやDBプーリングのクラスも提供されていることからFMDBを素直に使用したほうが結局のところ効率が良いと感じました。
ちなみにFMDBのライセンスはMITライセンスです。
以降はFMDBを使用することを前提としています。
FMDBのダウンロード
=========== 2014/3/31 追記 ===================
FMDBのインストールについて追記します。
以下、本記事に沿ってインストールしても問題はありませんが、CocoaPodsでインストールされることをオススメします。
OS Xはデフォルトでrubyがインストールされているので、以下コマンドですぐに利用できます。
・CocoaPodsのインストール
$ sudo gem install cocoapods
・FMDBのインストール
//FMDBをインストールしたいあなた自身が作成したプロジェクト配下へ移動します
$ cd [インストールしたいあなたのプロジェクト]
$ vi Podfile
Podfileを作成する。中身は以下。最新版をインストールするなら以下で良いでしょう。
pod 'FMDB'
・インストール実行
$ pod install
xcodeでワークスペースとして起動する。
$open YourApp.xcworkspace
するか
Finderで拡張子がxcworkspaceとなっている方を起動すると
既にFMDBがインストールされた状態となっているはずです。後はヘッダファイルを引き込めばOKですね。
因みに、CocoaPodsでインストールした場合は本記事の「FMDBのダウンロード」と「SQLiteのライブラリ取り込みとFMDBラッパークラスファイルの取り込み」の章はスキップして下さい。
Reference :
CocoaPods.
=============== 追記終わり ==================
FMDBはライブラリと言いましたが、ソースコードとして提供されているので、ダウンロードして必要なファイルをプロジェクトに追加します。
ソースはgithubで公開されています。
https://github.com/ccgus/fmdbブラウザでここを開いて直接ZIPファイルをダウンロードしても良いですし、Xcodeをインストールしているのであれば恐らくgitコマンドがインストールされていると思われるので以下の様にコマンドでgithubのFMDBプロジェクトのcloneをローカルに作成しても良いと思います。
gitを使う場合コマンドプロンプトを起動して、以下を実行します。実行カレントディレクトリ配下にfmdbのディレクトリが作成されます。
%git clone https://github.com/ccgus/fmdb.git
Xcode用のプロジェクトファイルもあるのでそのままXcodeで起動してコンパイル確認してみても良いですね。またテストコード用のソースコード(fmdb.m)も付属されており、このソースはFMDBを利用するに大変参考になります。
SQLiteのライブラリ取り込みとFMDBラッパークラスファイルの取り込み
・SQLiteのライブラリを追加
・FMDBのソースファイルをプロジェクトに追加
FMDB関連ファイルを格納するGroupフォルダを作成して格納した方がスッキリする。
DBファイルの事前作成とDBファイルのプロジェクト取り込み
SQLiteのDBファイルは事前に作成して良いし、アプリケーション上で作成しても良いです。今回は事前に作成しています。
コマンドプロンプトで以下を実行し、初期スキーマを生成しテーブルを一つ作成します。
MacBook-Pro:tmp naoyuki$ sqlite3 sample.db
SQLite version 3.7.7 2011-06-25 16:35:41Enter ".help" for instructionsEnter SQL statements terminated with a ";"
sqlite>create table example (i integer, n numeric, t text, r real);
sqlite> .schema
CREATE TABLE example (i integer, n numeric, t text, r real);
sqlite> .quit
MacBook-Pro:tmp naoyuki$
スキーマはこんな感じです。
CREATE TABLE example (
i integer,
n numeric,
t text,
r real
);
前述で作成したDBファイル(sample.db)をプロジェクトに取り込みます。プロジェクトへの取り込みはメニューの「File」-「Add Files to XXX」で表示されるダイアログから該当ファイルを選択してプロジェクトへ取り込みを行います。
初回起動時はDBファイルをコピーしてからDBオープン
ここまで長くなりましたがやっとここからコードを書きます。
まず初回時はDBスキーマが格納されているDBファイルを
/Documents/配下へコピーする必要があります。このへんの詳細は、
Resource Bundle/リソースバンドルと
iOS 標準ディレクトリパスの取得あたりを参照してもらえると理解出来るとおもいます。
以下、サンプルコードです。Document配下にDBファイルが無い場合はアプリケーションディレクトリからDBファイルをコピーしてからDBをオープンしています。
//DBファイル名
static NSString* const DB_FILE = @"sample.db";
@implementation SqliteExecute {
FMDatabase* _db;
}
- (BOOL)openDatabase {
//DBファイルへのパスを取得
//パスは~/Documents/配下に格納される。
NSString *dbPath = nil;
NSArray *documentsPath = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
//取得データ数を確認
if ([documentsPath count] >= 1) {
//固定で0番目を取得でOK
dbPath = [documentsPath objectAtIndex:0];
//パスの最後にファイル名をアペンドし、DBファイルへのフルパスを生成。
dbPath = [dbPath stringByAppendingPathComponent:DB_FILE];
NSLog(@"db path : %@", dbPath);
} else {
//error
NSLog(@"search Document path error. database file open error.");
return false;
}
//DBファイルがDocument配下に存在するか判定
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:dbPath]) {
//存在しない
//デフォルトのDBファイルをコピー(初回のみ)
//ファイルはアプリケーションディレクトリ配下に格納されている。
NSBundle *bundle = [NSBundle mainBundle];
NSString *orgPath = [bundle bundlePath];
//初期ファイルのパス。(~/XXX.app/sample.db)
orgPath = [orgPath stringByAppendingPathComponent:DB_FILE];
//デフォルトのDBファイルをDocument配下へコピー
if (![fileManager copyItemAtPath:orgPath toPath:dbPath error:nil]) {
//error
NSLog(@"db file copy error. : %@ to %@.", orgPath, dbPath);
return false;
}
}
//open database with FMDB.
_db = [FMDatabase databaseWithPath:dbPath];
return [_db open];
}
更新処理
FMDBのメソッドは大きく分けて更新系のメソッドと参照系のメソッドに分かれています。
更新系:- (BOOL)executeUpdate:(NSString*)sql, ...;
参照系:- (FMResultSet *)executeQuery:(NSString*)sql, ...;
上記メソッドは代表的なもので、この他にも引数が異なる形でいくつか提供されています。
以下、更新系のサンプルです。トランザクションを開始している点とステートメント再利用フラグを有効にしている点に注意して下さい。
- (BOOL)executeUpdate1 {
BOOL result = TRUE;
//トランザクション開始(exclusive)
[_db beginTransaction];
//ステートメントの再利用フラグ
//おそらくループ内で同一クエリの更新処理を行う場合バインドクエリの準備を何回
//も実行してしまうのためこのフラグを設定する。
//このフラグが設定されているとステートメントが再利用される。
[_db setShouldCacheStatements:YES];
//insertクエリ実行(プリミティブ型は使えない)
// [_db executeUpdate:@"insert into example values (?, ?, ?, ?)",
// 1, 2, @"test", 4.1];
// executeUpdateWithFormatメソッドで可能。
for (int i=1; i<=30; i++) {
[_db executeUpdate:@"insert into example values (?, ?, ?, ?)",
[NSNumber numberWithInt:i],
[NSDate date],
[NSString stringWithFormat:@"string : %d.", i],
[NSNumber numberWithFloat:(float)i/3]];
//check
if ([_db hadError]) {
result = FALSE;
NSLog(@"Err %d: %@", [_db lastErrorCode], [_db lastErrorMessage]);
}
}
//commit
[_db commit];
return result;
}
トランザクションは2種類のモードが提供されており、以下メソッドが提供されています。
- (BOOL)beginTransaction;
- (BOOL)beginDeferredTransaction;
・beginTransaction
begin exclusive transaction を指定してトランザクションを開始。
これは読み取り、書き込み共に排他されるロックモードです。
通常1スレッドで処理を行うのであれば、このモードで問題ありません。
・beginDeferredTransaction
begin deferred transaction を指定してトランザクションを開始。
これは必要に応じて最初のアクセス時にロックが働き、読み取りの場合は共有ロックで書き込みの場合は予約ロックがかけられるようです。因みにSQLiteのデフォルトロックモードは「Deferred」です。
参照処理
前述で登録したレコードを参照してみます。
・結果の取得をインデックスで行う場合。
- (NSArray*)executeQuery1 {
//結果格納用配列
NSMutableArray *result = [[NSMutableArray alloc] init];
//クエリの実行と結果(ResultSet)の取得
FMResultSet *rs = [_db executeQuery:@"select * from example"];
//結果の取得(indexで取得)
while ([rs next]) {
//結果格納用のオブジェクト
ExampleRecord *record = [[ExampleRecord alloc]init];
record.columnOfInteger = [rs intForColumnIndex:0];
record.columnOfDate = [rs dateForColumnIndex:1];
record.columnOfText = [rs stringForColumnIndex:2];
record.columnOfReal = [rs doubleForColumnIndex:3];
//配列に格納
[result addObject:record];
}
//close ResultSet.
[rs close];
return result;
}
・結果の取得をカラム名で行う場合
- (NSArray*)executeQuery2 {
//結果格納用配列
NSMutableArray *result = [[NSMutableArray alloc]init];
//クエリ実行
FMResultSet *rs = [_db executeQuery:@"select * from example where i = ?",
[NSNumber numberWithInt:2]];
// FMResultSet *rs = [_db executeQuery:@"select * from example where t like ?", @"%2."];
if ([_db hadError]) {
NSLog(@"Err %d: %@", [_db lastErrorCode], [_db lastErrorMessage]);
}
//結果の取得(カラム名指定)
while ([rs next]) {
//結果格納用オブジェクト
ExampleRecord *record = [[ExampleRecord alloc]init];
record.columnOfInteger = [rs intForColumn:@"i"];
record.columnOfDate = [rs dateForColumn:@"n"];
record.columnOfText = [rs stringForColumn:@"t"];
record.columnOfReal = [rs doubleForColumn:@"r"];
[result addObject:record];
}
//close ResultSet.
[rs close];
return result;
}
因みに、上のコードで登場しているExampleRecordというクラスは自前のクラスでレコードデータを格納する
クラスです。レコードクラスは無理に作成する必要はありませんが、参考までに載せておきます。
・ExampleRecord.h
@interface ExampleRecord : NSObject {
int i_;
NSDate *d_;
NSString *t_;
double r_;
}
// I'm using ARC.
@property (nonatomic) int columnOfInteger;
@property (nonatomic) NSDate* columnOfDate;
@property (nonatomic, copy) NSString* columnOfText;
@property (nonatomic) double columnOfReal;
@end
・ExampleRecord.m
#import "ExampleRecord.h"
@implementation ExampleRecord
@synthesize columnOfInteger = i_;
@synthesize columnOfDate = d_;
@synthesize columnOfText = t_;
@synthesize columnOfReal = r_;
@end
DBの解放
- (void)closeDatabase {
if (_db) {
[_db close];
}
}
ちょっと長くなってしまったので、続きは次回ということで。スレッド絡みは気になる所なので、そのあたりができたらと考えています。
マルチスレッド上での使用を考慮したFMDatabaseQueueについては
iOSでSQLiteを使う2(FMDBのFMDatabaseQueueクラスを使ってみる)の方に書いています。
=========== 2014/4/22 追記 ===================
FMDBを利用したサンプルプロジェクトをGitHubに上げていますので、参考にしてください。別件で作成したプロジェクトなので、若干本記事と異なる部分がありますが、初期化、参照、更新、バックグラウンド更新の処理があります。簡単なTableViewにAlertViewから入力した文字を表示するだけのアプリです。
GitHub : https://github.com/takuran/FMDBExample
$ git clone https://github.com/takuran/FMDBExample.git
$ cd FMDBExample
$ git submodule update --init --recursive
して下さい。
スクリーンショット:
削除はスワイプでね。
=============== 追記終わり ==================