iOSでSQLiteを使う

iPhoneアプリを作るときにSQLiteを使うことになり、FMDBというのが便利らしかったので利用してみました。

FMDBを組み込む

  1. FMDBからプロジェクトを落とす
  2. srcファイル内のfmdb.m以外をプロジェクトに入れる
  3. XcodeからFMDBを参照する

ざっくりですが、これでいけるはず

DBの用意

まず、DBとして使うファイルを指定し、開きます

static NSString* const DB_NAME = @"sample.db";

- (FMDatabase *) connectDB {
  NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *dir   = [paths objectAtIndex:0];
  NSString *db_path  = [dir stringByAppendingPathComponent:DB_NAME];
  NSFileManager *fm = [NSFileManager defaultManager];
  BOOL success = [fm fileExistsAtPath:db_path];
  // DB削除するとき
  /*
     if(success){
     [fm removeItemAtPath:db_path error:nil];
     ALog(@"delete db_file");
     success = false;
     }
   */

  if(!success){
    //データベースが存在しなければ、新規作成する
    success = [fm createFileAtPath:db_path contents:nil attributes:nil];
    if(!success){
      ALog(@"error in making file");
      return NO;
    }
  }
  ALog(@"success : %d, db_path is '%@'", success, db_path );
  return [FMDatabase databaseWithPath:db_path];
}

これで

// .hファイルで以下のように宣言してから
// @property FMDatabase *db;
self.db = [self connectDB];

とかすればOKです。

SQLの適用

SQLiteなので、SQLを書いてDBを操作します。
日時型はないので、REALとかTEXTで定義します。

CREATE TABLE

外部キー制約を有効化します。

// dbに接続
[self.db open];
// トランザクション開始
[self.db beginTransaction];
// 1つ目のsqlで外部キー制約の有効化
// 2つ目のsqlでテーブルが存在しなければusersテーブルを作成する
NSArray *initSQLs = @[@"PRAGMA foreign_keys=ON",
        @"CREATE TABLE if not exists users (id integer primary key autoincrement, name string, created real, modified real)"];
BOOL isSucceeded = YES;
// sqlの実行に失敗すればisSucceededをNOにする
for(NSString *initSQL in initSQLs) {
  if(![self.db executeUpdate:initSQL]) {
    isSucceeded = NO;
    break;
  }
}
if(isSucceeded){
  // sqlの実行に成功したらコミット
  ALog(@"created tables");
  [self.db commit];
} else {
  // sqlの実行に失敗すればロールバック
  ALog(@"ERROR!!!! fail to insert sample data. now, rollback");
  [self.db rollback];
}
// dbを閉じる
[self.db close];

executeUpdateはトランザクションで実行すること。

SELECT
[self.db open];
// プレースホルダを使う
NSString * sql = @"select * from users where id > ? order by modified desc";
// sqlを実行し、結果を取得(FMResultSet型)
// カンマ区切りでプレースホルダに入れる値を入れる
// int型はNSNumberに変換する
FMResultSet *result = [self.db executeQuery:sql, [NSNumber numberWithInt:5]];

NSMutableArray *names = [[NSMutableArray alloc] init];
// nextで次の結果へ行くのでwhile文で回す
while ([result next]) {
  // 結果の中から"name"というカラム名を取得
  [names addObject:[result stringForColumn:@"name"]];
}
// 結果を入れてたFMResultSetを閉じる
[result close];
[self.db close];

executeQueryの時はトランザクションにしなくていい

日時型について

上でも少し書きましたが、SQLiteには日時型がないのでREALやTEXTで代用します。
FMDBはNSDateをNSDate dateWithTimeIntervalSince1970メソッドでUNIX時刻に直してくれるらしい?ので、modifiedをupdateする時などはNSDate型を入れればいいらしい。
ちゃんと確認してません…。