iPhoneアプリでFMDBを使ったときのメモ

FMDBを使うとSQLiteを簡単に扱うことができます。
iPhoneアプリSQLiteを使用する必要があったので、この辺りを参考に導入しました。
SQLiteを利用する方法
iPhone Programming Tutorial – Creating a ToDo List Using SQLite Part 1

準備

FMDBをiPhoneアプリで使うには、以下の準備が必要です。


SQLiteをアプリから使えるようにする。
SQLiteのDBファイルをアプリのリソースに追加する。
・FMDBライブラリを組み込む

SQLiteをアプリから使えるように準備

Xcode
 Framework > 追加 > 既存のフレームワーク
 より、libsqlite3.0.dylibを追加する

SQLiteのDBファイルをアプリのリソースに追加

SQLiteのツールなどでデータベースファイルを作成し、XcodeからResourcesに追加する
最初から必要なデータなどを登録しておけるので便利

FMDBライブラリの組み込み

下記サイトより、fmdbをダウンロード
github fmdb


展開したファイルより、以下のファイルをXcodeから追加する

FMDatabase.h
FMDatabase.m
FMDatabaseAdditions.h
FMDatabaseAdditions.m
FMResultSet.h
FMResultSet.m

以上で使えるようになったはずです。

サンプル

かなりすっきりしたコードがかけます。
アカウント操作を行うモデルクラスをサンプルとしてつけておきます。

DB初期化クラス SqliteDB.h SqliteDB.m

UIApplicationDelegate継承クラスあたりで、アプリ起動時に一度だけよびだしてください。

 initializeDatabaseIfNeeded 
  SQLiteデータベースファイルが実行ディレクトリにない場合、リソースファイルからコピーして作成します。
  シミュレータのとき
   コピー先「/Users/ユーザ名/Library/Application Support/iPhone Simulator/バージョン/Applications/xxxxx」
   コピー先のファイルを削除すれば初期化できます。
 getDatabaseFilePath
  SQLiteデータベースファイルのパスを返します。

SqliteDB.h
#import <sqlite3.h>

#define BASEDB @"sample.sqlite"
#define DBPATH @"sampledata.sqlite"
#define DBFLAG @"dbflag"

@interface SqliteDB : NSObject {    
}

-(void) initializeDatabaseIfNeeded;

+(NSString*) getDatabaseFilePath;

@end
SqliteDB.m
#import "SqliteDB.h"


@implementation SqliteDB

-(void) initializeDatabaseIfNeeded {
    NSFileManager* fileManager = [NSFileManager defaultManager];
    NSError* error;
    
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString* documentsDir = [paths objectAtIndex:0];
    NSString* flagPath = [documentsDir stringByAppendingPathComponent:DBFLAG];

    // dbflag file check
    if (![fileManager fileExistsAtPath:flagPath]) {
        NSString* dbpath = [SqliteDB getDatabaseFilePath];
        NSString* templateDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:BASEDB];
        
        // remove database file
        if([fileManager fileExistsAtPath:dbpath] == YES) {
            [fileManager removeItemAtPath:dbpath error:NULL];
        }
        
        // copy database file
        if (![fileManager copyItemAtPath:templateDBPath toPath:dbpath error:&error]) {
            [error localizedDescription];
            return;
        }
        
        // dbflag file create
        [fileManager createFileAtPath:flagPath contents:nil attributes:nil];
    }

}

+(NSString*) getDatabaseFilePath {
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString* documentsDir = [paths objectAtIndex:0];
    return [documentsDir stringByAppendingPathComponent:DBPATH];
}


- (void)dealloc {
    [super dealloc];
}

@end
アカウントモデルクラス AccountModel.h, AccountModel.m

ユーザアカウント操作クラス。サンプルです。


 selectAll
  全レコード取得
 countAll
  全レコード件数取得
 selectById:(int)recordId
  レコード取得(ID)
 saveId:(int)recordId gender:(int)gender userName:(NSString*)userName
  レコード追加、更新
 deleteId:(int)recordId
  レコード削除

AccountModel.h
#import <sqlite3.h>
#import "FMDatabase.h"


@interface AccountModel : NSObject {
    int recordId;
    int gender;
    NSString* userName;
}

@property (nonatomic, readwrite) int recordId;
@property (nonatomic, readwrite) int gender;
@property (nonatomic, readwrite, retain) NSString* userName;

+(NSMutableArray*)selectAll;
+(int) countAll;
+(AccountModel*)selectById:(int)recordId;
+(int) saveId:(int)recordId gender:(int)gender userName:(NSString*)userName;
+(void) deleteId:(int)recordId;
  
// local
+(AccountModel*) createAccountModel:(FMResultSet*)rs;

@end
AccountModel.m
#import "AccountModel.h"
#import "SqliteDB.h"
#import <sqlite3.h>
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"


@implementation AccountModel

@synthesize recordId, gender, userName;

-(id) init {
    [super init];
    recordId = -1;
    return self;
}

+(int) countAll {
    NSString* dbPath = [SqliteDB getDatabaseFilePath];
    NSString* sql1 = [NSString stringWithFormat:@"select count(*) as count from Account"];
    
    FMDatabase* db = [FMDatabase databaseWithPath:dbPath];
    if(![db open]) {
        return 0;
    }
    [db setShouldCacheStatements:YES];
    
    FMResultSet* rs = nil;
    rs = [db executeQuery:sql1];
    
    int count = 0;
    if ([rs next]) {
        // just print out what we've got in a number of formats.
        count = [rs intForColumn:@"count"];
    }
    
    [rs close];
    [db close];
    
    return count;
}

/**
 *    select all
 */
+(NSMutableArray*)selectAll {
    NSString* dbPath = [SqliteDB getDatabaseFilePath];
    NSString* sql1 = [NSString stringWithFormat:@"select * from Account order by userName, gender, id"];
    NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease];
    
    FMDatabase* db = [FMDatabase databaseWithPath:dbPath];
    if(![db open]) {
        return nil;
    }
    [db setShouldCacheStatements:YES];
    
    FMResultSet* rs = nil;
    rs = [db executeQuery:sql1];
    
    while ([rs next]) {
        AccountModel* account = [AccountModel createAccountModel:rs];
        [ret addObject:account];
    }
    
    [rs close];
    [db close];
    
    return ret;
}

/**
 * select by id
 */
+(AccountModel*)selectById:(int)recordId {
    NSString* dbPath = [SqliteDB getDatabaseFilePath];
    NSString* sql = [NSString stringWithFormat:@"select * from Account where id = ?"];
    
    FMDatabase* db = [FMDatabase databaseWithPath:dbPath];
    if(![db open]) {
        return nil;
    }
    [db setShouldCacheStatements:YES];
    
    FMResultSet* rs = nil;
    rs = [db executeQuery:sql, [NSNumber numberWithInt:recordId]];
    
    AccountModel* account = nil;
    if ([rs next]) {
        account = [AccountModel createAccountModel:rs];
    }
    
    [rs close];
    [db close];
    
    return account;
}

+(AccountModel*) createAccountModel:(FMResultSet*)rs {
    AccountModel* account = [[[AccountModel alloc] init] autorelease];
    
    account.recordId = [rs intForColumn:@"id"];
    account.gender = [rs intForColumn:@"gender"];
    account.userName = [rs stringForColumn:@"userName"];

    return account;
}

/**
 *    save record
 */
+(int) saveId:(int)recordId gender:(int)gender userName:(NSString*)userName {
    NSString* dbPath = [SqliteDB getDatabaseFilePath];
    NSString* sql = [NSString stringWithFormat:@"%@%@",
                        @"insert or replace into Account (id, gender, userName)",
                     @" values (?, ?, ?)"];
    
    FMDatabase* db = [FMDatabase databaseWithPath:dbPath];
    if(![db open]) {
        return -1;
    }
    [db setShouldCacheStatements:YES];
    
    if (recordId == -1) {
        [db executeUpdate:sql, [NSNull null], [NSNumber numberWithInt:gender], userName];
    } else {
        [db executeUpdate:sql, [NSNumber numberWithInt:recordId], [NSNumber numberWithInt:gender], userName];
    }

    int lastInsertRowId = [db lastInsertRowId];
    [db close];
    
    return lastInsertRowId;
}

+(void) deleteId:(int)recordId {
    NSString* dbPath = [SqliteDB getDatabaseFilePath];
    NSString* sql = @"delete from Account where id = ?";
    
    FMDatabase* db = [FMDatabase databaseWithPath:dbPath];
    if(![db open]) {
        return;
    }
    [db setShouldCacheStatements:YES];
    
    [db executeUpdate:sql, [NSNumber numberWithInt:recordId]];
    
    [db close];
}

- (void)dealloc {
    [userName release];
    [super dealloc];
}

@end
使い方
// DBファイル初期化
SqliteDB* db = [[SqliteDB alloc] init];
[db initializeDatabaseIfNeeded];
[db release];

// account model 取得
AccountModel* account = [AccountModel selectById:1];


「FMDB」かなり使えます。おすすめです。