iOSでSQLiteを使う
FMDB
GitHub - ccgus/fmdb: A Cocoa / Objective-C wrapper around SQLite
設定
SQLiteを使うためには、「Link Binary With Libraries」にlibsqlite3.0.dylibを追加する必要があります。
ダウンロードしたFMDBのzipを解凍し、srcの中にあるFMDB関連のh,mファイルをプロジェクトに追加します。
例では、Frameworksグループの中にfmdbグループを作成し、その中に追加しました。
ファイルを追加する時は、Xcodeで右クリックして「Add Files to "XXXXX"」から行ったほうが無難です。
Finderからドラッグ&ドロップでファイルを追加して、ビルド時に次のようなエラーが発生した場合、「Build Phases」-「Compile Sources」にFMDBのmファイルを追加してあげると解決すると思います。
プログラム
私の場合、データベースアクセス処理はデータベースアクセス専用のクラスを作り、staticメソッドで実装しています。
データベースオブジェクトの取得
データベースにアクセスするために、データベースファイルを読み込んでFMDatabaseオブジェクトを作成します。
二種類試してみました。
Xcodeのプロジェクトの中にデータベースファイルを用意しておく場合
// Databaseオブジェクトの取得 +(FMDatabase *)getDB1 { NSString *dir = [[NSBundle mainBundle] resourcePath]; dir = [dir stringByAppendingPathComponent:@"db1.db"]; return [FMDatabase databaseWithPath:dir]; }
SQLiteManagerなどのツールであらかじめデータを入れておき、そのデータをアプリから使いたい場合などに使えます。
ただし、シミュレーターだとこの方式でもデータを更新できますが、実機ではreadonly databaseとなり、データの更新ができないようです。
実行時にFMDBに自動でデータベースファイルを作らせる場合
+(FMDatabase *)getDB2 { NSArray* paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES ); NSString* dir = [paths objectAtIndex:0]; return [FMDatabase databaseWithPath:[dir stringByAppendingPathComponent:@"db2.db"]]; }
アプリ内でデータを追加していく場合はこちらを使うことになると思います。
マスターデータなどはあらかじめ用意しておき、アプリ内でもデータを追加したい場合などは併用するのでしょうか・・。
アプリ初回起動時に、前者のデータを後者のデータに移す処理をしたりするのかもしれません。
FMDBにデータベースファイルを作らせる場合、必要なテーブルを作る処理も入れておきます。is not existsが使えるので便利ですね。
// テーブルがなければ作成 +(void)initDatabase2 { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sqlA = @"create table if not exists tableA(id integer primary key,name text);"; NSString *sqlB = @"create table if not exists tableB(id integer primary key,a_id integer,name text);"; [db open]; [db executeUpdate:sqlA]; [db executeUpdate:sqlB]; [db close]; }
データの更新
テーブルの全データを削除し、60,000件のデータを投入する例です。
+(void)insertManyData { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sqlD1 = @"delete from tableA"; NSString *sqlD2 = @"delete from tableB"; NSString *sqlA = @"insert into tableA(id,name) values(?,?)"; NSString *sqlB = @"insert into tableB(id,a_id,name) values(?,?,?)"; [db open]; // データ削除 [db executeUpdate:sqlD1]; [db executeUpdate:sqlD2]; // データ追加 for(int i = 0; i < 15000; i++) { NSString *AName = [NSString stringWithFormat:@"Aデータ%d", i]; [db executeUpdate:sqlA,[NSNumber numberWithInt:i], AName]; for(int j = 0; j < 3; j++) { NSString *BName = [NSString stringWithFormat:@"Bデータ%d_%d",i,j]; [db executeUpdate:sqlB,[NSNumber numberWithInt:(i * 10 + j)],[NSNumber numberWithInt: i],BName]; } } [db close]; }
データの検索
検索結果をNSDictionaryに格納していますが、検索結果の値がnilの場合は例外が発生します。
nilの場合、NSNullを格納すればいいようです。
・1件のデータを検索
// 1件のデータ検索 +(NSDictionary *)selectData:(NSInteger)id1 { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sql = @"select t1.id id1,t1.name name1,count(t2.id) cnt from tableA t1,tableB t2 where t1.id = t2.a_id and t1.id = ? group by t1.id,t1.name"; [db open]; FMResultSet *results = [db executeQuery:sql,[NSNumber numberWithInt:id1]]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; if([results next]) { [dictionary setValue:[NSNumber numberWithInt:[results intForColumn:@"id1"]] forKey:@"id1"]; [dictionary setValue:[results stringForColumn:@"name1"] forKey:@"name1"]; [dictionary setValue:[NSNumber numberWithInt:[results intForColumn:@"cnt"]] forKey:@"cnt"]; } [db close]; return [NSDictionary dictionaryWithDictionary:dictionary]; }
60,000件のデータから検索したときのレスポンス
インデックスなし | インデックスあり | |
---|---|---|
シミュレーター | 14ms | 2ms |
iPod touch(第4世代) | 100ms | 9ms |
iPad2 | 60ms | 4ms |
・データの一覧を検索する
// データ一覧検索 +(NSArray *)selectList { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sql = @"select t1.id id1,t1.name name1,count(t2.id) cnt from tableA t1,tableB t2 where t1.id = t2.a_id group by t1.id,t1.name limit 1000"; [db open]; FMResultSet *results = [db executeQuery:sql]; NSMutableArray *array = [NSMutableArray array]; while([results next]) { NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init ]; [dictionary setValue:[NSNumber numberWithInt:[results intForColumn:@"id1"]] forKey:@"id1"]; [dictionary setValue:[results stringForColumn:@"name1"] forKey:@"name1"]; [dictionary setValue:[NSNumber numberWithInt:[results intForColumn:@"cnt"]] forKey:@"cnt"]; [array addObject:dictionary]; [dictionary release]; } [db close]; return [NSArray arrayWithArray:array]; }
60,000件のデータから検索したときのレスポンス
インデックスなし | インデックスあり | |
---|---|---|
シミュレーター | 100ms | 15ms |
iPod touch(第4世代) | 1150ms | 150ms |
iPad2 | 600ms | 65ms |
インデックスを適切に設定すれば高速に動作します。
その他
インデックス作成・削除
// インデックス作成 +(void)createIndex { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sql = @"create index if not exists idx2 on tableB(a_id)"; [db open]; [db executeUpdate:sql]; [db close]; } // インデックス削除 +(void)dropIndex { FMDatabase *db = [DatabaseUtil getDB2]; NSString *sql = @"drop index if exists idx2"; [db open]; [db executeUpdate:sql]; [db close]; }
参考にさせていただいたサイト
iOS で SQLite - FMDB の使い方 - アカベコマイリ