NSManagedObjectModelクラス

2011. 03. 27
●概要

NSManagedObjectModelオブジェクトは、スキーマ(アプリケーション内で使用するエンティティのコレクション(データモデル))を表現するものです。

モデルは、スキーマ内のエンティティを表す一つ以上のNSEntityDescriptionオブジェクトを含みます。

各NSEntityDescriptionオブジェクトは、スキーマ内のエンティティのプロパティ(またはフィールド)を表す、プロパティ記述オブジェクト(NSPropertyDescriptionのサブクラスのインスタンス)を持ちます。

Core Dataフレームワークは、いくつかの方法でこの記述を使用します。
  • Interface Builder内で作成されるUIへの制約
  • 実行時における属性と関連値の有効化
  • 管理オブジェクトとデータベースまたはオブジェクトの永続化のためのファイルベースのスキーマ間とのマッピング
管理オブジェクトモデルは、各エンティティオブジェクトと、対応するCore Dataフレームワーク内の永続ストレージ構造を使用するための管理オブジェクトクラスとの間をマッピングして保持します。

エンティティのメソッドによって、特定の管理オブジェクトのためのエンティティを決定することができます。

通常は、Xcodeのデータモデリングツールを使用して管理オブジェクトモデルを生成しますが、必要に応じてプログラムでモデルを構築することも可能です。


モデルファイルの読み込み

管理オブジェクトモデルファイルは、通常はプロジェクトまたはフレームワーク内に格納されています。

モデルを読み込むには、コンストラクタへURLを提供します。

モデルの読み込みは、そのエンティティの全てを読み込むわけではないことに注意してください。


フェッチ要求の格納

多くの場合、アプリケーションでは共通する特徴を持つ部分の、オブジェクトのコレクションを取得しようとします。

時には予めそれらの特徴(プロパティ値)を定義したり、必要ならば実行時に値を指定することもできます。

例えば、自身の持つ全てのPixar映画を取得できるようにしたり、あるいは実行時にユーザが指定したものより多い全ての映画を取得できるようにしたい場合があります。

フェッチ要求は、多くの場合テンプレートとして管理オブジェクトモデルで予め定義されています。

それらはモデル内でのクエリとパラメータを名前付きで事前に定義することができます。

NSManagedObjectModelは名前によって格納されているフェッチ要求を取得するためのAPIを提供し、変数の代入を実行します。
fetchRequestTemplateForName:fetchRequestFromTemplateWithName:substitutionVariables:を参照)

フェッチ要求テンプレートはプログラムで生成することも出来、setFetchRequestTemplate:forName:を使用してモデルと関連付けしますが、一般的には通常Xcodeの設計ツールを使用して定義します。


構成

時々、モデルは(特に一つのフレームワークの場合)様々な状況で使用されるので、様々な状況で使用するためにエンティティの異なるセットを指定する必要があるかもしれません。

例えば、ユーザが管理者権限を持っている場合のみ、特定のエンティティを使用可能にするかもしれません。

この要件をサポートするためには、モデルは複数の構成を持つことができます。

各構成はエンティティの名前と関連するセットを持っています。

セットは重複する場合があります。

プログラムで構成を構築する場合、setEntities:forConfiguration:またはXcodeの設計ツールを使用し、entitiesForConfiguration:を使用して指定した構成の名前のエンティティを取得します。


モデルの変更

モデルは永続ストアのデータの構造を記述しているので、少しでもモデルを変更するとスキーマが変わるため、予め生成されたストアとの互換性が無くなります(したがって開くことができません)。

スキーマを変更する場合は、現在のストアのデータを新しいバージョンに移行する必要があります。
Core Data Model Versioning and Data Migration Programming Guideを参照)

例えば、現在のエンティティに新たなエンティティや属性を追加する場合、既存のストアは開くことができなくなります。

有効な制約または属性の新たなデフォルト値を設定することで、既存のストアを開くことができるようになります。


プログラムでのモデルの編集

管理オブジェクトモデルは、オブジェクトグラフマネージャ(管理オブジェクトコンテキストまたは永続ストアコーディネータ)によって使用されるまでは編集可能です。

これらは動的に生成または変更をすることができます。

ただし一度使用したモデルは変更しないでください。

実行時に適用した場合、オブジェクトマネージャが最初のモデルを使ってデータをフェッチすると、そのモデル全体が編集できなくなります。

それ以降の時点でモデルやサブオブジェクトの変更を行うと例外が発生します。

使用中のモデルを変更する必要がある場合は、コピーを生成し、コピーを変更して、その後古いモデルのオブジェクトを破棄してください。


高速列挙

Mac OS X v10.5以降とiOSでは、NSManagedObjectModelはNSFastEnumerationプロトコルをサポートしています。

モデルのエンティティを列挙するために使用した例を以下に示します。

NSManagedObjectModel *aModel = ...;

for (NSEntityDescription *entity in aModel) {

    // entity is each instance of NSEntityDescription in aModel in turn

}



●タスク

モデルの初期化

– initWithContentsOfURL:
+ mergedModelFromBundles:
+ mergedModelFromBundles:forStoreMetadata:
+ modelByMergingModels:
+ modelByMergingModels:forStoreMetadata:

エンティティと構成

– entities
– entitiesByName
– setEntities:
– configurations
– entitiesForConfiguration:
– setEntities:forConfiguration:

フェッチ要求テンプレートの取得

– fetchRequestTemplatesByName
– fetchRequestTemplateForName:
– fetchRequestFromTemplateWithName:substitutionVariables:
– setFetchRequestTemplate:forName:

ローカライズ


– localizationDictionary
– setLocalizationDictionary:

バージョン管理とマイグレーション

– isConfiguration:compatibleWithStoreMetadata:
– entityVersionHashesByName
– versionIdentifiers
– setVersionIdentifiers:



●クラスメソッド

mergedModelFromBundles:

+ (NSManagedObjectModel *)mergedModelFromBundles:(NSArray *)bundles

指定したバンドル内で見つかった、全てのモデルを結合して生成したモデルを返します。

bundles:検索するNSBundleのインスタンスの配列です。

nilを指定した場合はメインバンドルが検索されます。



●インスタンスメソッド

initWithContentsOfURL:

- (id)initWithContentsOfURL:(NSURL *)url

指定したURLにあるモデルファイルを使用してレシーバを初期化します。

戻り値は、urlにあるファイルを使用して初期化された管理オブジェクトモデルです。

url:モデルファイルの場所を指定するURLオブジェクトです。



参考文献

NSManagedObjectModel Class Reference

NSPersistentStoreCoordinatorクラス

2011. 03. 21
●概要

NSPersistentStoreCoordinatorのインスタンスは、(種類別の)永続ストアとモデル(より正確に言うとモデルの設定)を関連付けし、永続ストアまたはストアと管理オブジェクトコンテキストまたはコンテキストの間を仲介する役割を果たします。

NSManagedObjectContextのインスタンスは、永続ストアへのオブジェクトグラフの保存や、モデル情報の取得にコーディネータを使用します。

コンテキストはコーディネータ無しには完全に機能せず、コーディネータを通さなければモデルにアクセスできません。

コーディネータは、永続ストアのグループを集合したストアとして表し、管理オブジェクトコンテキストへの入口を提示するように設計されています。

管理オブジェクトコンテキストは、コーディネータが扱う全てのデータストアをまとめ、それを基礎にオブジェクトグラフを生成することができます。

コーディネータは、それと同時にシリアライズ処理も提供します。

複数のスレッドで異なる書き込み処理を行う場合は、複数のコーディネータを使用してください。

コーディネータで複数スレッドを直接処理する場合、明示的にロックとアンロックをする必要があることに注意してください。

各コーディネータ(とそのコンテナ)は管理オブジェクトコンテキストの異なるコピーを使用するため、異なるバージョンになる可能性があります。

これによりファイルのバージョン管理を正確に処理することができます。

コーディネータは、その基礎となるオブジェクトストアへアクセスすることができます。

オブジェクトストアを取得するには、最初に(addPersistentStoreWithType:configuration:URL:options:error:を使用して)、またはpersistentStoreForURL:persistentStoresを使用して追加します。

これらは例えばストアが既に追加されている、または同じストアから2つのオブジェクトを取得する場合によって決定されます。

・ある場所からストアを移動する、またはストアのタイプを変更する場合
 migratePersistentStore:toURL:options:withType:error:

・永続ストアコーディネータを使用して、指定したストアからメタデータを設定する場合
 setMetadata:forPersistentStore:

これらのタスクの詳細については、『Core Dataプログラミングガイド』の『永続ストアの使い方』を参照してください。



●タスク

●登録されたストアのタイプ

+ registeredStoreTypes
+ registerStoreClass:forStoreType:

●コーディネータの初期化

– initWithManagedObjectModel:
– managedObjectModel

●永続ストアの設定

– addPersistentStoreWithType:configuration:URL:options:error:
– setURL:forPersistentStore:
– removePersistentStore:error:
– migratePersistentStore:toURL:options:withType:error:
– persistentStores
– persistentStoreForURL:
– URLForPersistentStore:

●ロッキング

– lock
– tryLock
– unlock

●メタデータの処理

– metadataForPersistentStore:
– setMetadata:forPersistentStore:
+ setMetadata:forPersistentStoreOfType:URL:error:
+ metadataForPersistentStoreOfType:URL:error:

●オブジェクトIDの検出

– managedObjectIDForURIRepresentation:



●クラスメソッド



●インスタンスメソッド

addPersistentStoreWithType:configuration:URL:options:error:

- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error

指定した場所にある指定した型の新しい永続ストアを追加し、新しいストアを返します。

戻り値は新しく生成されたストア、またはエラーが発生した場合はnilを返します。

storeType:ストア型を指定する文字列定数(例えばNSSQLiteStoreTypeなど)を指定します。
(可能な値は『Store Types』を参照してください)

configuration:新しいストアによって使用される、レシーバの管理オブジェクトモデル内の構成の名前を指定します。

storeURL:永続ストアのファイルの場所を指定します。

options:ストアを読み込みのみにするかどうかと、(XMLストアの場合は)読み込む前にXMLファイルがDTDに対して検証するかどうかを指定するキー値ペアを含む辞書を指定します。
キーの定義については『Store Options』と『Migration Options』を参照してください。
この値はnilの場合があります。

error:新しいストアが生成できない場合に、問題を説明するNSErrorのインスタンスを含めて返します。


initWithManagedObjectModel:

- (id)initWithManagedObjectModel:(NSManagedObjectModel *)model

管理オブジェクトモデルを含むレシーバを初期化します。

戻り値はmodelを含む初期化されたレシーバです。

model:管理オブジェクトモデルを指定します。


managedObjectModel

- (NSManagedObjectModel *)managedObjectModel

レシーバの管理オブジェクトモデルを返します。



●定数

Store Types

永続ストアの型です。

NSString * const NSSQLiteStoreType;
NSString * const NSBinaryStoreType;
NSString * const NSInMemoryStoreType;

NSSQLiteStoreType
SQLiteデータベースのストア型。

NSBinaryStoreType
バイナリのストア型。

NSInMemoryStoreType
インメモリのストア型。



●通知



参考文献

NSPersistentStoreCoordinator Class Reference

Wikipedia/Document Type Definition

Persistence(13)~SQLite3への保存(7)

2011. 03. 10
前回の『Persistence(12)~SQLite3への保存(6)』では旧版の本書とiPhone OS 3.x版のサンプルコードとの差異を説明しましたので、今回はiOS 4.x版のサンプルコードとの差異を説明します。


●sqlite3オブジェクトの宣言

旧版の本書の内容と元のiPhone OS 3.x版のサンプルコードでは、sqlite3オブジェクトをヘッダファイルで宣言しており、単一のsqlite3オブジェクトをviewDidLoadapplicationWillTerminate:(マルチタスク対応として、ここではapplicationWillResignActive:に変更)の両メソッド間で共有し、SQLiteデータベースの一連の流れがメソッドを跨いで進行していました。

しかし、最新のiOS 4.x版に倣ってソースファイルでメソッド毎にsqlite3オブジェクトの宣言をした結果、メソッド間で同名の異なるsqlite3オブジェクトを扱う形になり、流れが寸断して例外エラーを発生させる原因となりました。

iOS 4.x版のサンプルコードはその辺が考慮されており、メソッド単位でSQLiteデータベースの一連の流れが完結しています。


●applicationWillResignActive:の宣言

iOS 4.x版のサンプルコードでは、ヘッダファイルPersistenceViewController.hでapplicationWillResignActive:の宣言を行っていますが、UIApplicationDelegateプロトコルのインスタンスメソッドですので特に宣言をしなくても正常に動作します。
(宣言をする理由が分からないので、不安な方は宣言しておいた方がいいかもです)


●iOS 4.x版との差異

前回のサンプルコードからの差異でいうと、viewDidLoadとapplicationWillResignActive:両メソッド間に跨がっていたSQLiteデータベースの一連の流れをメソッド単位で完結させるというもので、具体的にはviewDidLoadにsqlite3_close()を追加してデータベースを閉じ、applicationWillResignActive:にsqlite3_open()を追加してデータベースを開き直すだけです。
(太字が追加した部分)

- (void)applicationWillResignActive:(UIApplication *)application {
    sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Failed to Open database");
    }


    for (int i = 1; i <= 4; i++) {
        NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", i];
        UITextField *field = [self valueForKey:fieldName];
        [fieldName release];

        char *update = "INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA) VALUES (?, ?);";
        char *errorMsg;
        sqlite3_stmt *stmt;
        if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
            sqlite3_bind_int(stmt, 1, i);
            sqlite3_bind_text(stmt, 2, [field.text UTF8String], -1, NULL);
        }
        if (sqlite3_step(stmt) != SQLITE_DONE)
            NSAssert1(0, @"Error updating tables: %s", errorMsg);
        sqlite3_finalize(stmt);
    }
    sqlite3_close(database);
}

#pragma mark -

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];

    sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Failed to Open database");
    }

    char *errorMsg;
    NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS (ROW INTEGER PRIMARY KEY, FIELD_DATA TEXT);";
    if (sqlite3_exec (database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert1(0, @"Error creating table: %s", errorMsg);
    }

    NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
        while (sqlite3_step(statement) == SQLITE_ROW) {
            int row = sqlite3_column_int(statement, 0);
            char *rowData = (char *)sqlite3_column_text(statement, 1);
            NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", row];
            NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
            UITextField *field = [self valueForKey:fieldName];
            field.text = fieldValue;
            [fieldName release];
            [fieldValue release];
        }
        sqlite3_finalize(statement);
    }
    sqlite3_close(database);

    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
}

2035

両メソッドのsqlite3オブジェクトは同名なので、単純にコピー&ペーストで追加しただけです。


●実行

これでアサーションが発生すること無く、正常に読み込みと書き込みができるようになります。



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

Persistence(12)〜SQLite3への保存(6)

2011. 03. 09
旧版の本書のサンプルコードとiPhone OS 3.x版のサンプルコードとの差異を比較します。


●applicationWillResignActive:への修正

Persistence(9)〜SQLite3への保存(3)』でも説明しましたが、iPhone OS 3.x版はマルチタスク未対応であったため、ファイル書き込みのタイミングがapplicationWillTerminate:(アプリケーションの終了直前)となっており、そのままでは現在のiOS 4.x以降でファイルへの書き込みができないので、applicationWillResignActive:に修正する必要があります。
(修正箇所は上記ページで確認してください)


●iPhone OS 3.x版との差異

旧版の本書とiPhone OS 3.x版では、データを読み込みviewDidLoadに違いはなく、データを書き込むapplicationWillResignActive:の内容が異なります。

相違点は以下の通りです。

1)SQLステートメントupdateを、NSStringオブジェクトではなくcharオブジェクトで生成
2)INSERTステートメントで、VALUESキーワードでの値の設定をワイルドカード『?』で指定
3)sqlite3_exec()ではなくsqlite3_prepare_v2()sqlite3_step()sqlite3_finalize()を使用
4)sqlite3_bind_*()によるワイルドカード『?』へのバインド
5)sqlite3_free()によるエラーメッセージオブジェクトの解放の省略

iPhone OS 3.x版の修正を加えたapplicationWillResignActive:は以下のようになります。
(太字が追加・修正した部分)

- (void)applicationWillResignActive:(UIApplication *)application {
    sqlite3 *database;
    for (int i = 1; i <= 4; i++) {
        NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", i];
        UITextField *field = [self valueForKey:fieldName];
        [fieldName release];

        char *update = "INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA) VALUES (?, ?);";
        char *errorMsg;
        sqlite3_stmt *stmt;
        if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
            sqlite3_bind_int(stmt, 1, i);
            sqlite3_bind_text(stmt, 2, [field.text UTF8String], -1, NULL);
        }
        if (sqlite3_step(stmt) != SQLITE_DONE)
            NSAssert1(0, @"Error updating tables: %s", errorMsg);
        sqlite3_finalize(stmt);

    }
    sqlite3_close(database);
}

2033.jpg

1)INSERTステートメントの設定

NSStringオブジェクトとして生成していたINSERTステートメントを、charオブジェクトとして生成しています。

これは変数の指定にテキストフィールドのインスタンスやNSStringのメソッドを使用せず、ワイルドカード『?』を使っているため、Objective-Cで書く必要が無いからです。

2)VALUESキーワードのワイルドカード指定

4つのテキストフィールドの値はforループで設定するため、格納する値は各テキストフィールドを指す変数を指定する必要があります。

旧版の本書ではテキストフィールドのインスタンスのテキストを変換し、直接INSERTステートメントに指定するという、Objective-C言語で記述を行っていました。

iPhone OS 3.x版では変数指定の部分をワイルドカード『?』(バインド変数)とし、sqlite3_bind_*()ルーチンを使用することによってC言語での記述に改められています。

3)SQLステートメント実行部分の変更

sqlite3_exec()ではsqlite3_bind_*()ルーチンを記述できないので、SQLステートメントの実行部分をsqlite3_prepare_v2()sqlite3_step()sqlite3_finalize()による記述に変更しています。

4)準備ステートメントでのバインド変数の指定

sqlite3_prepare_v2()で準備ステートメントを生成する際に、バインド変数の組み込みを行います。

sqlite3_prepare_v2()の記述自体はsqlite3_exec()で行っていたものと大差なく、sqlite3のオブジェクトdatabaseに対し、C言語文字列で記述したINSERTステートメントupdateを指定しています。

準備ステートメントのコンパイルに成功したら、バインド変数を組み込みます。

1つ目のバインド変数は列ROWの整数値ですので、sqlite3_bind_int()でコンパイル済みステートメントstmtの1つ目にiを指定しています。

2つ目のバインド変数は列FIELD_DATAのテキスト値ですので、sqlite3_bind_text()でコンパイル済みステートメントstmtの2つ目に、テキストフィールドのテキスト値をUTF8StringメソッドでC文字列に変換して指定しています。

sqlite3_bind_text()の第4引数は第3引数で渡すテキスト値のバイト数で、-1を渡すことで自動指定することになります。

第5引数はSQLiteの処理が完了した後で文字列の処理に使用するデストラクタで、ここではNULLとしています。

5)sqlite3_free()の省略

iPhone OS 3.x版では、sqlite3_free()によるエラーメッセージオブジェクトの解放は省略されています。

6)SQLステートメントの実行

sqlite3_step()でバインド変数を組み込んだINSERTステートメントを実行し、正常に完了するとSQLITE_DONEが返されますので、そうでない場合はアサーションを生成します。

1つの行の処理が終わったらsqlite3_finalize()でステートメントを破棄し、次の行にループします。

4つの行の処理が終わったら、sqlite3_close()でデータベースファイルを閉じます。


●実行

applicationWillTerminate:をapplicationWillResignActive:に修正したiPhone OS 3.x版のサンプルコードではアサーションが発生しませんが、上記のサンプルコードを実行してアプリケーションを非アクティブにすると、デバッガコンソールに『Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error updating tables: 8”ˇøD6'』というテーブル更新時のアサーションが出力されます。

これはsqlite3オブジェクトdatabaseをヘッダファイルで宣言するかソースファイルで宣言するかの違いに起因します。

旧版およびiPhone OS 3.x版では、viewDidLoadでsqlite3_open()を使用して開いたsqlite3オブジェクトをメソッド内で閉じず、applicationWillResignActive:においてsqlite3_close()で閉じています。

ここでは両メソッドの冒頭でsqlite3オブジェクトをそれぞれ宣言しているので、applicationWillResignActive:は開いていないデータベースに対して処理を行う形となり、アサーションが出る結果になっています。

実際ソースファイルでの宣言を止めて、iPhone OS 3.x版のサンプルコードと同様にヘッダファイルで宣言する形に変更するとアサーションが発生せず、正常に書き込みができるようになります。



参考文献

IT用語辞典/バインド

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

Persistence(11)〜SQLite3への保存(5)

2011. 03. 08
3)applicationWillResignActive:の編集

applicationWillResignActive:では、テキストフィールドのデータを取得し、SQLiteのテーブルに入力し、データベースファイルを保存します。

最初にsqlite3のオブジェクトdatabaseを作成し、4つのテキストフィールドからデータを取得するためのforループを作成します。

データの取得は、まずinitWithFormat:メソッドでテキストフィールドのプロパティ名field*を作成し、NSString文字列のfieldNameとして保持します。

そして『Persistence(10)〜SQLite3への保存(4)』で説明したviewDidLoadと同様に、fieldNameをキーとしてvalueForKey:メソッドを呼び出し、UITextFieldのインスタンスfieldとします。

次にinitWithFormat:メソッドで、テーブルにデータを挿入/置換を行うSQLステートメントを作成します。

INSERTステートメントはテーブルに単一行を作成するもので、REPLACEステートメントは既存の行を置換します。

REPLACEステートメントは後方互換のため単独での利用もできますが、INSERTステートメントと合わせて『INSERT OR REPLACE』として使用することが推奨されています。

今回のINSERTステートメントは、VALUESキーワードを使用して列リストに値を挿入します。

『INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA) VALUES (%d, '%@');』の場合、テーブルFIELDS内にある列ROWと列FIELD_DATAにそれぞれ整数(forループ変数のi)と文字列(テキストフィールドのテキストfield.text)を挿入ないし置換を行うことになります。

データの挿入/置換を行うNSStringオブジェクトのSQLステートメントupdateを使って、sqlite3_exec()内でUTF8StringメソッドでC文字列に変換して実行されます。

挿入/置換に失敗した場合、NSAssert1でアサーションを生成し、sqlite3_free()でエラーメッセージの解放をしています。

このsqlite3_free()での解放ですが、viewDidLoadでのアサーションの時は行っていませんし、iPhone OS 3.x版iOS 4.x版のサンプルコードでは記述されていないので不要なのかもしれません。

4つのテキストフィールドの文字列を行に納めたら、sqlite3_close()でsqlite3オブジェクトを破棄し、データベースファイルを閉じます。


●実行

Persistence(9)〜SQLite3への保存(3)』で説明しましたが、この旧版のサンプルコードはデータベースファイルの読み込みはできますが、書き込みはできません。

理由の1つは、applicationWillResignActive:のINSERTステートメントで文字列を挿入する際、(SQLステートメントはC文字列にする必要があるのに)変数をC文字列に変換していないためです。

実際にこのサンプルコードを実行してアプリケーションを非アクティブにすると、デバッガコンソールに『Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error updating tables: 8”ˇøD6'』というテーブル更新時のアサーションが出力されます。

このエラーはテキストフィールドの文字列field.textをUTF8StringメソッドでC文字列に変換すると解消されます。
(太字が修正した部分)

- (void)applicationWillResignActive:(UIApplication *)application {
    sqlite3 *database;
    for (int i = 1; i <= 4; i++) {
        NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", i];
        UITextField *field = [self valueForKey:fieldName];
        [fieldName release];

        NSString *update = [[NSString alloc]
        initWithFormat:@"INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA)
        VALUES (%d, '%@');", i, [field.text UTF8String]];
        char *errorMsg;
        if (sqlite3_exec (database, [update UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
            NSAssert1(0, @"Error updating tables: %s", errorMsg);
            sqlite3_free(errorMsg);
        }
    }
    sqlite3_close(database);
}

2031.jpg

この修正で例外エラーは発生しなくなりますが、SQLデータベースファイルへの保存はできません。

その理由は、旧版がINSERTステートメントでの変数の組み込みをNSStringオブジェクトで行っているのに対し、(正常に書き込みができる)iPhone OS 3.x版iOS 4.x版のサンプルコードsqlite3_bind()を使用していることに起因していると思われます。



参考文献

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

Persistence(10)~SQLite3への保存(4)

2011. 03. 07
Persistence(9)~SQLite3への保存(3)』で述べたように、旧版とiPhone OS 3.x版、iOS 4.x版のサンプルコードで若干差異がありますので、まず旧版の内容を確認していきます。

なお、ここではiOS 4.2に対応するため、データ保存のターゲットをapplicationWillTerminate:からapplicationWillResignActive:に変更しています。


●ビューコントローラのソースファイルPersistenceViewController.mの編集

Persistence(7)~SQLite3への保存(1)』のヘッダファイルの編集でも説明しましたが、旧版の本書ではsqlite3.hのインポートとsqlite3オブジェクトの宣言をヘッダファイルPersistenceViewController.hで行っていますが、ここではソースファイルで行っています。

それと、データの入出力を行っているapplicationWillResignActive:とviewDidLoadの改修を行います。
(太字が追加・修正した部分)

#import "PersistenceViewController.h"
#import "FourLines.h"
#import <sqlite3.h>

@implementation PersistenceViewController

@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

- (NSString *)dataFilePath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

- (void)applicationWillResignActive:(UIApplication *)application {
    sqlite3 *database;
    for (int i = 1; i <= 4; i++) {
        NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", i];
        UITextField *field = [self valueForKey:fieldName];
        [fieldName release];

        NSString *update = [[NSString alloc] initWithFormat:@"INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA) VALUES (%d, '%@');", i, field.text];
        char *errorMsg;
        if (sqlite3_exec (database, [update UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
            NSAssert1(0, @"Error updating tables: %s", errorMsg);
            sqlite3_free(errorMsg);
        }
    }
    sqlite3_close(database);

}

#pragma mark -

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];

    sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Failed to Open database");
    }

    char *errorMsg;
    NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS (ROW INTEGER PRIMARY KEY, FIELD_DATA TEXT);";
    if (sqlite3_exec (database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert1(0, @"Error creating table: %s", errorMsg);
    }

    NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
        while (sqlite3_step(statement) == SQLITE_ROW) {
            int row = sqlite3_column_int(statement, 0);
            char *rowData = (char *)sqlite3_column_text(statement, 1);
            NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", row];
            NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
            UITextField *field = [self valueForKey:fieldName];
            field.text = fieldValue;
            [fieldName release];
            [fieldValue release];
        }
        sqlite3_finalize(statement);
    }


    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    [super viewDidUnload];
    self.field1 = nil;
    self.field2 = nil;
    self.field3 = nil;
    self.field4 = nil;
}

- (void)dealloc {
    [field1 release];
    [field2 release];
    [field3 release];
    [field4 release];
    [super dealloc];
}

@end

2019

1)sqlite3.hのインポート

旧版の本書ではヘッダファイルPersistenceViewController.hでインポートしていますが、ここではソースファイルのPersistenceViewController.mでsqlite3.hをインポートしています。

プロジェクト内のファイルではないので、『""』ではなく『<>』で囲っています。

『#import <sqlite3.h>』の『sqlite3』をドラッグして選択し、右クリックで『定義へジャンプ』を選択するとsqlite3.hファイルが開きます。

2018

ファイルヒストリー・メニューにカーソルを載せて少し待つと黄色いポップアップでファイルパスが表示されます。

旧版の本書と若干書き方が違いますが、同じファイル(/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.2.sdk/usr/include/sqlite3.h)を指していることが確認できます。

2012

2)viewDidLoadの編集

SQLiteのデータ処理の都合上、先にviewDidLoadから説明します。

viewDidLoadでは、SQLiteのデータベースファイルを開いてテーブルを生成し、そこから取得したデータをテキストフィールドに設定しています。

最初にsqlite3のオブジェクトdatabaseを作成し、sqlite3_open()でデータベースファイルを開きます。

第1引数はデータベースファイルのパスを指定するもので、dataFilePathメソッドで取得したNSStringのファイルパスをUTF8StringメソッドでC文字列に変換して指定しています。

第2引数には先程作成したsqlite3オブジェクトのdatabaseのポインタを指定します。

データベースファイルが正常に開けたかどうかをSQLITE_OKで判別し、失敗した場合にはsqlite3_close()でデータベースファイルを閉じ、NSAssertでアサーションを生成します。

アサーションとは詳解 Objective-C 2.0によると、『デバッグ目的でプログラムに埋め込み、条件判定付きで例外を発生させる仕組み』とあります。

ここでのsqlite3_open()でのデータベースファイルが開けなかった場合、『NSAssert(0, @"Failed to Open database")』とありますので、第1引数である条件が0、つまり無条件で偽となって例外が発生し、NSStringオブジェクトとして第2引数の文字列『Failed to Open database』が渡されます。

アサーションはデバッグが完了して製品版を作成する際など、コンパイル時にプリプロセッサマクロ『NS_BLOCK_ASSERTIONS』を定義すると無効になり、コードに組み込まれなくなりますので、一括で処理できます。

データベースファイルが正常に開けたら、次にCREATE TABLEでsqlite3オブジェクトdatabase内にテーブルを生成します。

IF NOT EXISTS句が指定されていますので、既に同名のテーブルが存在する場合はこのステートメントは無視されます。

ここではテーブル名をFIELDS、1つ目の列名をROWとしたINTEGER(整数)型、2つ目の列名をFIELD_DATAとしたTEXT(テキスト)型としたテーブルになります。

1つ目の列の型のPRIMARY KEYは列に制限を加えるもので、INTEGER PRIMARY KEYの場合は『DBOnline/INTEGER PRIMARY KEYが設定されたカラム』によると、重複しない整数の連番を自動的に割り振るとあります。
(特に指定しなかった場合は1から割り振られます)

したがって、テーブルFIELDSは下表のような形になります。

ROW
(INTEGER型) 
FIELD_DATA
(TEXT型) 
1 
2 
3 
4 

CREATE TABLEステートメントはNSStringのcreateSQLに一旦格納し、sqlite3_exec()内でUTF8StringメソッドでC文字列に変換して実行されます。

sqlite3_exec()はデータベースオブジェクトに対してSQLステートメントを実行するもので、sqlite3_prepare_v2()、sqlite3_step()、sqlite3_finalize()の3つを一纏めにしたラッパーです。

第1引数でデータベースオブジェクトdatabaseを、第2引数で実行するステートメントcreateSQLを指定しています。

第3引数はコールバック関数、第4引数はコールバック関数への引数を指定します。

コールバック関数は『IT用語辞典/コールバック関数とは』によると、『呼び出し先から任意に呼び出す関数』とあります。

SQLiteの場合『idocsq.net/[SQLite3] sqlite3_execによるSQL文の実行(1) - 文字列のSQL文を直接実行』を参照しますと、CREATE TABLEステートメントを実行した際、SELECTステートメントの実行時に1行毎に実行される関数ならびにその引数を指定するのですが、ここでは両方にNULLが指定されており、コールバック関数は無いということになります。

第5引数はSQLステートメントを実行した際のエラーメッセージの文字列を格納するもので、char型のオブジェクトerrorMsgのポインタを指定しています。

テーブルの生成に失敗した場合、sqlite3_close()でデータベースファイルを閉じ、NSAssert1でアサーションを生成します。

NSAssert1はNSAssertに引数が1つ加えられたもので、例外文字列に引数の文字列を加えることができます。

『NSAssert1(0, @"Error creating table: %s", errorMsg)』の場合、テーブル生成に失敗した理由がerrorMsgに格納され、errorMsgの文字列を加えた『Error creating table: ~』という例外が無条件で発生します。

続いて生成したテーブルからSELECTステートメントでデータを取得します。

『SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW』の場合、FROM句は入力データを指し、ORDER BY句は返す順番を指定するものなので、『テーブルFIELDSから列ROWと列FIELD_DATAのデータを、行の昇順で取得する』ことになります。

取り出したデータをテキストフィールドに反映させるという細かな操作のため、ラッパーのsqlite3_exec()ではなく、sqlite3_prepare_v2()とsqlite3_step()、sqlite3_finalize()で実行しています。

sqlite3_prepare_v2()はSQLステートメントの準備を行うもので、要約は『Persistence(8)~SQLite3への保存(2)』を参照してください。

ここではsqlite3オブジェクトのdatabaseに対し、上記のSELECTステートメントqueryをUTF8StringメソッドでC文字列に変換して指定、第3引数は-1ですのでステートメントのバイト数を自動取得に、コンパイル済みステートメントを受け皿としてsqlite3_stmtオブジェクトのstatementを指定、第5引数はnilですのでSELECTステートメント1つのみの実行となります。

SQLステートメントのコンパイルに成功したら、whileループでsqlite3_step()を実行し、列ROWと列FIELD_DATAに入ってる全ての行のデータを取得します。

sqlite3_step()はsqlite3_prepare_v2()でコンパイルしたSQLステートメントを実行するものです。

whileループの条件であるSQLITE_ROWは、sqlite3_step()で行を取得した際に次の行が存在することを示しており、次の行が無い場合はSQLITE_DONEが返ってくるので、列に存在する全ての行に対して実行するということになります。

sqlite3_step()のwhileループ内では、ROWとFIELD_DATAの両列から行毎にデータを取得し、テキストフィールドの特定とテキストフィールドへの値の設定を行います。

sqlite3_column_int()は整数、sqlite3_column_text()はテキストのデータを取得するルーチンで、第1引数は処理内容であるSQLステートメントのstatement、第2引数は0から始まる整数の列番号を指定します。

列番号01
列名ROWFIELD_DATA
列型INTEGERTEXT
取得ルーチンsqlite3_column_int()sqlite3_column_text()
取得変数rowrowData

sqlite3_column_int()で取得したint値rowはinitWithFormat:メソッドでアウトレットfield*を指すNSString文字列fieldNameに、sqlite3_column_text()で取得したchar文字列rowDataはinitWithUTF8String:メソッドでテキストフィールドの値となるNSString文字列fieldValueに変換します。

テキストフィールドの値の設定には、アウトレットを指すfieldNameをキーとしてvalueForKey:メソッドを呼び出すことでプロパティの値が得られるので、それをUITextFieldのインスタンスfieldとし、textプロパティでfieldにfieldValueの値を設定します。

テキストフィールド値の設定が終わったらfieldNameとfieldValueを解放し、次の行の処理を行います。

全ての行の値が取得し終わったらsqlite3_finalize()でステートメントを破棄します。


– (const char *)UTF8String

NULL終端のUTF8エンコーディングのC文字列を返します。

戻り値であるC文字列は、元になるNSStringオブジェクトの文字列が解放される、あるいは自動解放が行われると消滅しますので、必要な場合はコピーを取って下さい。


NSAssert

#define NSAssert(conditiondesc)

指定した条件が偽の場合にアサーションを生成します。

NSAssertマクロは条件を評価し、アサーションハンドラへのフロントエンドとして機能します。

各スレッドはNSAssertionHandlerクラスのオブジェクトである独自のアサーションハンドラを持っています。

呼び出された時、アサーションハンドラはメソッドやクラス名(または関数名)を含むエラーメッセージを出力します。

そしてNSInternalInconsistencyException例外を発生させます。

conditionの評価がNOの場合、マクロは現在のスレッドのアサーションハンドラ上にあるhandleFailureInMethod:object:file:lineNumber:description:を呼び出し、説明の文字列としてdescを渡します。

このマクロはObjective-Cメソッド内でのみ使用する必要があります。

アサーションは、プリプロセッサマクロNS_BLOCK_ASSERTIONSが定義されている場合は無効になります。

condition:YESまたはNOで評価できる式を指定します。

desc:偽状態を説明するエラーメッセージを含むNSStringオブジェクトを指定します。


NSAssert1

#define NSAssert1(conditiondescarg1)

指定した条件が偽の場合にアサーションを生成します。

NSAssert1マクロは条件を評価し、アサーションハンドラへのフロントエンドとして機能します。

各スレッドはNSAssertionHandlerクラスのオブジェクトである独自のアサーションハンドラを持っています。

呼び出された時、アサーションハンドラはメソッドやクラス名(または関数名)を含むエラーメッセージを出力します。

そしてNSInternalInconsistencyException例外を発生させます。

conditionの評価がNOの場合、マクロは現在のスレッドのアサーションハンドラ上にあるhandleFailureInMethod:object:file:lineNumber:description:を呼び出し、説明の文字列としてdescと、代入変数としてarg1を渡します。

このマクロはObjective-Cメソッド内でのみ使用する必要があります。

アサーションは、プリプロセッサマクロNS_BLOCK_ASSERTIONSが定義されている場合は無効になります。

condition:YESまたはNOで評価できる式を指定します。

desc:偽状態を説明するエラーメッセージを含むprintfスタイルの文字列を含むNSStringオブジェクトと、1つの引数のためのプレースホルダを指定します。

arg1:descの(プレースホルダの)場所に挿入する引数を指定します。


initWithUTF8String:

– (id)initWithUTF8String:(const)bytes

NULLで終わるUTF-8エンコーディングのC文字列のコピーで初期化したNSStringオブジェクトを返します。
(NULL終端でない場合は例外が発生します)

bytes:UTF-8エンコーディングのNULL終端C文字列。


valueForKey:

- (id)valueForKey:(NSString *)key

指定したキーで識別されたプロパティの値を返します。

valueForKey:を使用した正しい値を検索して返す検索パターンについては、『Key-Value Coding Programming Guide』にある『Accessor Search Implementation Details』を参照してください。

key:レシーバのプロパティの内の一つの名前を指定します。



参考文献

NSString Class Reference

Foundation Functions Reference

NSKeyValueCoding Protocol Reference

DBOnline/INTEGER PRIMARY KEYが設定されたカラム

idocsq.net/[SQLite3] sqlite3_execによるSQL文の実行(1) - 文字列のSQL文を直接実行

IT用語辞典/コールバック関数とは

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

準備ステートメントへの値のバインド

2011. 03. 06
int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
int sqlite3_bind_double(sqlite3_stmt*, int, double);
int sqlite3_bind_int(sqlite3_stmt*, int, int);
int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
int sqlite3_bind_null(sqlite3_stmt*, int);
int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);

SQLステートメントでsqlite3_prepare_v2()やその変種へテキスト入力する場合に、以下のテンプレートのいずれかに一致するparameterによって文字を置換することができます。

・?
・?NNN
・:VVV
・@VVV
・$VVV

上記のテンプレートにおいて、NNNは整数文字を、VVVは英数字を表す識別子です。

これらのパラメータの値(『ホストパラメータ名』または『SQLパラメータ』とも呼ばれる)は、ここで定義されているsqlite3_bind_*()ルーチンを使用して設定することができます。

sqlite3_bind_*()ルーチンへの第1引数は常に、sqlite3_prepare_v2()またはその変種から返されるsqlite3_stmtオブジェクトへのポインタです。

第2引数はSQLパラメータのインデックスを設定します。

左端のSQLパラメータは1のインデックスを持っています。

同じ名前のSQLパラメータを複数回使用する場合は、2つ目以降も最初のものと同じインデックスを持ちます。

名付けられたパラメータへのインデックスは、sqlite3_bind_parameter_index() APIを必要に応じて使用して検索することができます。

『?NNN』パラメータへのインデックスは、NNNの値です。

NNNの値は1とsqlite3_limit()パラメータのSQLITE_LIMIT_VARIABLE_NUMBER(デフォルト値:999)の間である必要があります。

第3引数はパラメータへバインドする値です。

第4引数を持つこれらのルーチンにおいて、その値はパラメータのバイト数です。

正確に言うと、その値は値のバイト数であり、文字数ではありません。

第4引数が負の値の場合、文字列の長さは最初のゼロ終端までのバイト数です。

sqlite3_bind_blob()、sqlite3_bind_text()、sqlite3_bind_text16()の第5引数は、SQLiteの処理が完了した後でBLOBまたは文字列の処理に使用するデストラクタです。

デストラクタは、sqlite3_bind_blob()、sqlite3_bind_text()、sqlite3_bind_text16()の呼び出しが失敗した場合でも、BLOBまたは文字列を処理するために呼び出されます。

第5引数が特殊な値SQLITE_STATICの場合、SQLiteは情報は静的なものと前提され、空間の管理や解放をする必要はありません。

第5引数が値SQLITE_TRANSIENTの場合、SQLiteはsqlite3_bind_*()ルーチンが返す前に、すぐに自身のデータのプライベートコピーを作成します。

sqlite3_bind_zeroblob()ルーチンは、ゼロで満たされた長さNのBLOBをバインドします。

zeroblobは処理されている間(ちょうど整数を保持するサイズの)固定されたメモリ量を使用します。

zeroblobはincremental BLOB I/Oルーチンを使用して書かれた後の内容で、BLOBのプレースホルダとして機能するように意図されています。

zeroblobが負の値の場合はBLOBがゼロの長さになります。

prepared statementのためのNULLポインタを指定して、またはsqlite3_reset()より最近に呼び出されたsqlite3_step()のためのprepared statementを指定して、sqlite3_bind_*()ルーチンのいずれかが呼び出された場合、SQLITE_MISUSEが返されます。

sqlite3_bind_()ルーチンが破棄されたprepared statementを渡された場合、未定義でおそらく危険な結果となります。

バインディングはsqlite3_reset()ルーチンによってクリアされません。

バインドされていないパラメータはNULLとして解釈されます。

sqlite3_bind_*ルーチンは、成功した場合にはSQLITE_OKを、何か問題があった場合はerror codeを返します。

SQLITE_RANGEは、パラメータのインデックスが範囲外の場合に返されます。

SQLITE_NOMEMは、malloc()が失敗した場合に返されます。

sqlite3_bind_parameter_count()sqlite3_bind_parameter_name()sqlite3_bind_parameter_index()も参照してください。

ObjectsConstantsFunctionsの一覧を参照してください。



参考文献

Binding Values To Prepared Statements

0 CommentsPosted in SQLite

Persistence(9)~SQLite3への保存(3)

2011. 03. 05
●サンプルコードの差異

ビューコントローラのソースファイルPersistenceViewController.mの内容ですが、旧版の本書とiPhone OS 3.x版のサンプルコード、そしてiOS 4.x版のサンプルコードでは内容が若干異なります。

そのままの状態で実行した場合、旧版ならびにiPhone OS 3.x版のサンプルコードでは、テキストフィールドに入力したテキストを保存することができません。
(シミュレータのOSがiOS 4.2の場合)

iOS 4.x版のサンプルコードは正常にSQLファイルの読み書きが行われるため、iOS 4.x版で書き込んだデータは、旧版ならびにiPhone OS 3.x版のサンプルコードでも読み込めるのですが、そこからの変更は反映されません。

ただしiPhone OS 3.x版の不具合は、当時マルチタスク非対応であったため、ファイル書き込みのタイミングがapplicationWillTerminate:(アプリケーションの終了直前)となっていることが原因で、タイミングをapplicationWillResignActive:(アプリケーションが非アクティブになる直前)に書き換えると正常に書き込まれます。

修正箇所は、3カ所になります。

PersistenceViewController.h)

・applicationWillTerminate:の宣言の削除

PersistenceViewController.m)

・applicationWillTerminate:の実装部分の一行目をapplicationWillResignActive:に変更
(メソッドの内容は変更無し)

・viewDidLoadの実装部分で、通知センターでのセレクタの呼び出しをapplicationWillTerminate:からapplicationWillResignActive:に変更

しかし旧版のサンプルコードはapplicationWillTerminate:からapplicationWillResignActive:に変更していても正常に書き込まれません。

3つのサンプルコードにおける、SQLite絡みの実装部分(viewDidLoadとapplicationWillResignActive:)の差異は下表の通りです。

 旧版iPhone OS 3.x版iOS 4.x版
viewDidLoadsqlite3_open()sqlite3_open()sqlite3_open()
sqlite3_exec()
(CREATE TABLE)
sqlite3_exec()
(CREATE TABLE)
sqlite3_exec()
(CREATE TABLE)
sqlite3_prepare_v2()
(SELECT)
sqlite3_prepare_v2()
(SELECT)
sqlite3_prepare_v2()
(SELECT)
sqlite3_step()sqlite3_step()sqlite3_step()
sqlite3_finalize()sqlite3_finalize()sqlite3_finalize()
  sqlite3_close()
applicationWillResignActive:  sqlite3_open()
sqlite3_exec()
(INSERT) 
sqlite3_prepare_v2()
(INSERT) 
sqlite3_prepare_v2()
(INSERT)
sqlite3_step()sqlite3_step()
sqlite3_finalize()sqlite3_finalize()
sqlite3_close()sqlite3_close()sqlite3_close()

viewDidLoadでは、SQLデータファイルのテーブルからデータを読み込み、テキストフィールドの値を取得するのですが、iOS 4.x版で最後にsqlite3_close()で一旦閉じている以外は3つとも同じ内容です。

applicationWillResignActive:では、テキストフィールドの値をSQLデータファイルのテーブルに書き込みを行っています。

iOS 4.x版で、viewDidLoadで一旦閉じたデータベースを再度sqlite3_open()で開き直している以外はiPhone OS 3.x版と内容は同じで、sqlite3_prepare_v2()とsqlite3_step()、sqlite3_finalize()を使用しています。

しかし旧版はほぼ同じ内容ながら、ラッパーであるsqlite3_exec()を使用しています。

これはテーブルに書き込む行番号と対応するテキストフィールド文字列をSQLステートメントに組み込む際、旧版ではNSStringオブジェクトとしてSQLステートメントを生成しているのに対し、iPhone OS 3.x版およびiOS 4.x版ではcharオブジェクトとしてSQLステートメントを生成し、行番号とテキストフィールド文字列の組み込みにワイルドカード指定でsqlite3_bind()を利用しているという違いのためです。

sqlite3_bind()を利用するためにはsqlite3_stmtオブジェクトが必要なため、sqlite3_exec()ではなく、sqlite3_prepare_v2()~sqlite3_finalize()を使用しています。

旧版が正常に書き込みできない理由は、sqlite3_bind()を利用せずに、NSStringオブジェクトで行番号とテキストフィールド文字列の組み込みを行ったSQLステートメントの生成をしているためと思われます。

※旧版のサンプルコードには、テキストフィールド文字列の組み込み時にエラーを引き起こす記述があるのですが、その問題を修正しても正常に出力されません。

詳細については以降で触れていきたいと思います。



参考文献

Second Flush/SQLiteのC/C++インターフェイスの導入

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

Beginning Ios 6 Development: Exploring the Ios SdkBeginning Ios 6 Development: Exploring the Ios Sdk
(2012/12/26)
David Mark、Jack Nutting 他

商品詳細を見る

Persistence(8)~SQLite3への保存(2)

2011. 03. 04
iOSアプリケーションでSQLite3を扱う場合の要点を軽く説明します。


●SQLite3を扱う準備

Persistence(7)~SQLite3への保存(1)』で記述していますが、iOSアプリケーションでSQLite3を扱うには、

・プロジェクトへのダイナミックライブラリlibsqlite3.0.dylibの追加
・sqlite3.hのインポート

が必要になります。


●SQLite3の流れ

SQLite3データベースファイルの取り扱いは、以下のような流れになります。

sqlite3_open()データベースファイルを開く
 
sqlite3_prepare()SQLステートメントの準備
 
sqlite3_step()SQLステートメントの評価
 
sqlite3_finalize()SQLステートメントの破棄
 
sqlite3_close()データベースファイルを閉じる

SQLステートメントは、データベースファイル内にあるテーブルへの操作内容を記述する文で、テーブルの作成や行の挿入/置換、値の設定などを行います。

sqlite3_prepare()で操作内容を記述し、sqlite3_step()で操作の実行、sqlite3_finalize()で操作内容の破棄を行います。

また、SQLステートメントの実行部分であるsqlite3_prepare()、sqlite3_step()、sqlite3_finalize()をまとめて行うラッパーとしてsqlite3_exec()に置き換えることができます。

sqlite3_open()データベースファイルを開く
 
sqlite3_exec()SQLステートメントの実行
 
sqlite3_close()データベースファイルを閉じる


●sqlite3_open()

sqlite3_open()は、指定したファイル名のSQLデータベースファイルを開くルーチンで、指定したファイルが無い場合は新規に生成されます。

データベースにアクセスするため、第2引数にsqlite3オブジェクトのポインタを与えます。

sqlite3_open()には、sqlite3_open()、sqlite3_open16()、sqlite3_open_v2()の3種類があります。

idocsq.net/[SQLite3] データベースのオープンとクローズ / sqlite3オブジェクト / 終了コード』によりますと、開こうとするデータベースファイル名のエンコーディングがUTF-8の場合はsqlite3_open()かsqlite3_open_v2()、UTF-16の場合はsqlite3_open16()を使うとあります。

現在はオプション指定ができるsqlite3_open_v2()の使用が推奨されているようですが、ここでは単純なsqlite3_open()を使用します。


●sqlite3_prepare()

sqlite3_prepare()は『CodeZine/SQLiteで“おこづかいちょう” (2/5)』によると、データベースを操作する処理手順を記述したSQLステートメントを事前にコンパイルするルーチンとあります。

第1引数に処理するデータベースのsqlite3オブジェクトのポインタを指定します。

第2引数には操作内容を記述したSQLステートメントを指定します。

第3引数はSQLステートメントのバイト数を指定するのですが、-1と指定することでSQLステートメントがゼロ終端されていると判断され、自分でバイト数を数えなくても自動で割り出されます。

第4引数ではコンパイルしたSQLステートメントの出力先であるsqlite3_stmtのオブジェクトを指定します。
(処理が終わったらsqlite3_finalize()でこのオブジェクトを破棄します)

第5引数はSQLステートメントの未使用部分へのポインタで、SQLステートメントに複数の処理を記述する場合に設定するもので、単一の処理の場合はnilを設定します。

sqlite3_prepare()には、sqlite3_prepare()、sqlite3_prepare_v2()、sqlite3_prepare16()、sqlite3_prepare16_v2()の4種類があります。

指定するSQLステートメントのエンコーディングがUTF-8の場合はsqlite3_prepare()かsqlite3_prepare_v2()を、UTF-16の場合にはsqlite3_prepare16()かsqlite3_prepare16_v2()を使用します。

sqlite3_prepare()とsqlite3_prepare16()は下位互換のための古いルーチンで、現在は新しいsqlite3_prepare_v2()またはsqlite3_prepare16_v2()の使用が推奨されており、ここでもsqlite3_prepare_v2()を使用します。


●sqlite3_step()

sqlite3_step()は、sqlite3_prepare()で生成したコンパイル済みのSQLステートメント(sqlite3_stmtオブジェクト)を実行するルーチンです。

sqlite3_step()は、データベースのテーブルからSQLステートメントの検索条件に合う行を検索し、一行ずつ取り出し処理を行います。

検索条件に合う行が複数ある場合は、SQLITE_ROWが返されて自動的に次の行に進み、無くなった場合はSQLITE_DONEが返されるので、ループ処理の判別に利用できます。


●sqlite3_finalize()

sqlite3_finalize()は、sqlite3_prepare()で生成したSQLステートメント(sqlite3_stmtオブジェクト)を削除します。


●sqlite3_exec()

sqlite3_exec()は、sqlite3_prepare_v2()sqlite3_step()sqlite3_finalize()の3つのルーチンをまとめて一つのルーチンで呼び出すことができるラッパーです。


●sqlite3_close()

sqlite3_close()は、sqlite3_open()で開いたデータベース(sqlite3オブジェクト)を閉じるルーチンです。



参考文献

Second Flush/SQLiteのC/C++インターフェイスの導入

混沌のiPhone開発ブログ(カテゴリー/SQLite3)

idocsq.net/[SQLite3] データベースのオープンとクローズ / sqlite3オブジェクト / 終了コード

CodeZine/SQLiteで“おこづかいちょう” (2/5)






Lifestyle 650 home entertainment system
Calendar
02 | 2011/03 | 04
Sun Mon Tue Wed Thu Fri Sat
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

Lifestyle 650 home entertainment system
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR