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 他

商品詳細を見る






Lifestyle 650 home entertainment system
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
10 | 2017/11 | 12
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 - -
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