iPhoneでSQLite3を使う6(DBのエラー処理)

今回はDBのエラー処理を実装していきます。エラー処理を放置していましたので作りこんで行きます。
基本方針として、メソッド毎に戻り値を見てエラー処理をするのは面倒なので、try catchを使ってエラーをさばいていくことにします。
try catchを使えば、try {}の間で、例外(Exception)が発生したら。catch {]に飛びその中でエラー処理することができます。


準備として、DB用の例外クラスを作成します。Exceptionのクラス型ごとにcatch節を書くことができるので、エラー処理を振り分けるためです。
NSExceptionを派生させて、エラーのコードど発生元のメソッド名を保持できるようにします。また、エラーコードはtypedefで定義しておきます。ついでにエラーコード対応したメッセージも戻せるようにしておきましょう。

  • DBException.h
#import <Foundation/Foundation.h>
#import <Foundation/NSException.h>

// Exeption Codeは、DBで発生したエラーの種類をCodeで表したものです。
typedef enum _DBExceptionCode {
	DB_OPEN_ERROR = 0,
	DB_INSERT_ERROR,
	DB_UPDATE_ERROR,
	DB_DELETE_ERROR,
	DB_SELECT_ERROR,
	DB_PREPARE_ERROR,
	DB_FINALIZE_ERROR
} DBExceptionCode;

@interface DBException : NSException {
	// Exeption Codeを保持するメンバ変数
	DBExceptionCode Code_;
    
	// エラー発生した場所(クラス名/メソッド名)を保持するメンバ変数
	NSString* Method_;
}
@property (nonatomic) DBExceptionCode Code;

-(NSString*)message;
-(NSString*)method;

+(id)exceptionWithCode:(DBExceptionCode) Code
                  Name:(NSString*) Name
                Method:(NSString*) Method;

-(id)initWithCode:(DBExceptionCode) Code
             Name:(NSString*) Name
	       Method:(NSString*) Method;

@end
  • DBException.m
#import "DBException.h"

@implementation DBException

@synthesize Code = Code_;

+(id)exceptionWithCode:(DBExceptionCode) Code
				  Name:(NSString*) Name
				Method:(NSString*) Method
{
	id exception = [[self alloc] initWithCode:(DBExceptionCode) Code
                                         Name:(NSString*) Name
                                       Method:(NSString*) Method];
    
	return exception;
}

-(id)initWithCode:(DBExceptionCode) Code
             Name:(NSString*) Name
           Method:(NSString*) Method
{
    self = [super initWithName:Name reason:nil userInfo:nil];
	if (self)
	{
		self.Code = Code;
		Method_ = Method;
	}
    
	return self;
}

-(NSString*)message {
	// Exeption Code値に応じたメッセージを返す
	NSString* Message;
	switch(Code_){
		case DB_OPEN_ERROR:
			Message = NSLocalizedString(@"DBOpen", @"message");
			break;
		case DB_INSERT_ERROR:
			Message = NSLocalizedString(@"DBInsert", @"message");
			break;
		case DB_UPDATE_ERROR:
			Message = NSLocalizedString(@"DBUpdate", @"message");
			break;
		case DB_DELETE_ERROR:
			Message = NSLocalizedString(@"DBDelete", @"message");
			break;
		case DB_PREPARE_ERROR:
			Message = NSLocalizedString(@"DBPrepare", @"message");
			break;
		case DB_FINALIZE_ERROR:
			Message = NSLocalizedString(@"DBfinalyze", @"message");
			break;
		default:
			Message = @"";
			break;
	}
	return Message;
}

-(NSString*)method {
	return Method_;
}

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

@end

dbOpenやその他のDB関連のメソッドを書き換えていきます。dbOpenは下記のようになります。他のメソッドは割愛。

-(void)dbOpen {
	NSLog(@"DB Open");

	NSString* database_filename;
	NSString* template_path;
	NSString* database_path;
	NSString* work_path;

	// データベース名
	database_filename = @"twitterDB.sqlite";

	// データベースファイルを格納するために文書フォルダーを取得します。
	work_path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

	// データベースファイルのパスを取得します。
	database_path = [NSString stringWithFormat:@"%@/%@", work_path, database_filename];

	// 文書フォルダーにデータベースファイルが存在しているかを確認します。
	NSFileManager* manager = [NSFileManager defaultManager];

	if (![manager fileExistsAtPath:database_path])
	{
		NSError* error = nil;

		// 文書フォルダーに存在しない場合は、データベースの複製元をバンドルから取得します。
		template_path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:database_filename];

		// バンドルから取得したデータベースファイルを文書フォルダーにコピーします。
		if (![manager copyItemAtPath:template_path toPath:database_path error:&error])
		{
			// データベースファイルのコピーに失敗した場合の処理です。
			DBException* exception =
				[DBException exceptionWithCode:DB_OPEN_ERROR
							 	           Name:@"DB Error"
									Method:[NSString stringWithFormat:@"%@/%@",
										      NSStringFromClass([self class]),
										      NSStringFromSelector(_cmd)]];
			@throw exception;
		}
	}

	// 文書フォルダーに用意されたデータベースファイルを開きます。
	if (sqlite3_open([database_path UTF8String], &db_) != SQLITE_OK)
	{
		// データベースファイルを SQLite で開くことに失敗しました。
		DBException* exception =
			[DBException exceptionWithCode:DB_OPEN_ERROR
								   Name:@"DB Error"
								Method:[NSString stringWithFormat:@"%@/%@",
									      NSStringFromClass([self class]),
									      NSStringFromSelector(_cmd)]];
			@throw exception;
	}
}

書き換えが終わったら、DB編 第2回で作った骨格にエラー処理を埋め込みます。

  • dbOpenに失敗したら、dbCloseは通らずログをはいて終了。・・・外側のtry catchで処理
  • DB処理(InsertとかUpdate)に失敗したら、dbRollbackした後にdbCloseして終了。・・・内側のtry catchで処理
  • 全て成功したら、dbCommitした後にdbCloseして終了。

という形に組むと下記のようになります。

@try {
	// DB接続
	[self dbOpen];

	@try {
		// トランザクションの開始
		[self dbBegin];

		// DB処理
		 ・
		 ・
		 ・
		// DBの更新を確定する。
		[self dbCommit];
	} @catch (DBException* ex) {
		// DBエラーの処理を記述
		NSLog(@"%@ %@(%@)", [ex name], [ex message], [ex method]);
		// DBをもとの状態に戻す。
		[self dbRollback];
	}
	// DB切断
	[self dbClose];
// catch節に、@catch (DBException* ex)と書くと、DBExceptionを捕まえてくれます。
} @catch (DBException* ex) {
	// DB Openエラーの処理を記述
	NSLog(@"%@ %@(%@)", [ex name], [ex message], [ex method]);
// DBExceptionじゃない場合は、次のcatch節で処理されます。
} @catch (NSException* ex) {
	// その他エラーの処理を記述
	NSLog(@"%@/%@",[ex name], [ex reason]);
} @finally {
	// 必要なら実装
}

以上、エラー処理のざくっとしたイメージです。
※あくまでイメージです、机上で組んでるので、間違えているかもしれません。休みの日にでも実際に組んで動かしてみますので、動かなければごめんなさい。