Apple iPhone 3GとiPhone SDKを使ってアプリケーションをデバッグするための情報。

ホーム / Hardware / iPhone SDK

更新:11/10/02 | iPhone SDKでデバッグ| iPhone SDK Tech

デバッグとは

言わずもがな、不覚にもアプリケーションが正しく動かなかった場合に原因を突き止めて修正する作業のことです。

NSLogの利用

NSLogを利用してタイムスタンプや変数の内容等をデバッグウィンドウに表示することで、原因を特定できることが多々あります。例えばNilであるはずがないオブジェクトが存在していることを突き止めれば、これをインスタンス化したり代入している箇所に問題があると言えます。

ただ、NSLogをDevice用のアプリケーションに入れておくとログが物理的に生成されてしまうため、スイッチを付けて判定させると便利です。

#if kShouldPrintLog
NSLog(@"*** init_bydate_statement defined");
#endif

こんな感じです。kShouldPrintLogを1にしている場合だけログが出力されます。問題点は 、ソースコードが大変見通し悪くなる点です。

ブレークポイント

コマンド+\や、コードウィンドウの左側をクリックすることでブレークポイントを設定することが出来ます。プログラムは、指定した部分を通ると一時停止してカーソルを変数にあてると実際の値を見ることも出来ます。(Out of scopeで見えないこともあり、あまりあてにしてはいけません)

ブレークポイントはまとめて一時的に無効にしたり削除したりすることも出来ます。最近は、特定の箇所を通ったかどうか程度にしか使えない気がしています。変数の内容を表示したいのであればNSLogを使った方が確実で、クラッシュ箇所を特定したいのであれば次の方法を検討した方が確実です。

ツールバーの足跡

2010-01-17 18:11:34.959 App[25836:207] *** -[Record retain]: message sent to deallocated instance 0x452dd10

といったエラーが出て止まった際に、Xcodeのウィンドウからエラーに至るまでの足跡情報を表示させて、項目によっては該当するコードに移動することが出来ます。

iPhoneのデバッグ

上の場合、-[ほにゃらら]という部分を選択すると該当するコードに移動できる場合が多いです。

デバッグウィンドウからスタック情報を参照

予め、Xcodeの実行可能ファイルを選択して、次のオプションを追加しておきます。
MallocStackLogging

デバッグウィンドウ

このオプションは、シミュレーターでしか使えないので注意が必要です。実行するプラットフォームを変える度にオプションを変更しなくてはならないので面倒ですが、次のコマンドを入力することでスタック情報を参照することが出来て、大変強力な情報を参照することが出来ます。

2010-01-17 18:24:51.741 App[26067:207] *** -[Record retain]: message sent to deallocated instance 0x4553480
(gdb)
(gdb) info malloc-history 0x4553480
Alloc: Block address: 0x04553480 length: 76
Stack - pthread: 0xa0a4b500 number of frames: 18
0: 0x98b008a3 in malloc_zone_calloc
1: 0x98b007fa in calloc
2: 0x9576d02f in _internal_class_createInstanceFromZone
3: 0x9576d1f7 in _internal_class_createInstance
4: 0x237f8fc in +[NSObject allocWithZone:] <----この辺りが怪しい
5: 0x237f7ea in +[NSObject alloc]
6: 0x4a04 in -[AppDelegate initializeRecord] at /Users/minoru/Documents/iPhoneSDK/App/Classes/AppDelegate.m:846
7: 0xad99 in -[RootViewController moveToAccountView] at /Users/name/Documents/iPhoneSDK/App/Classes/RootViewController.m:510
8: 0x3c0a9a in -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
9: 0x3bc8d8 in -[UITableView _userSelectRowAtIndexPath:]
10: 0x1a131ba in __NSFireDelayedPerform
11: 0x23c2ac0 in CFRunLoopRunSpecific
12: 0x23c1c48 in CFRunLoopRunInMode
13: 0x29ba78d in GSEventRunModal
14: 0x29ba852 in GSEventRun
15: 0x37e003 in UIApplicationMain
16: 0x2496 in main at /Users/name/Documents/iPhoneSDK/App/main.m:15
17: 0x2416 in start
(gdb)

 

EXC_BAD_ACCESSに有効なNSZombieEnabled

EXC_BAD_ACCESSは良く目にする不正終了メッセージの一つです。突然gdbにEXC_BAD_ACCESSが表示され終了しますが、前述のツールバーの足跡を追っても原因が釈然としないことが多いのが悩みの種です。

そこで、実行可能ファイルの実行時引数に次のオプションを追加します。

NSZombieEnabled:YES

すると、以下のように何が原因かを具体的に示してくれます。
以下の場合、Recordオブジェクトが既にdeallocされているにもかかわらずreleaseを投げていることが原因となっています。(それでもやっかいなのは、コード中で明示的にreleaseなんて投げていないという事実なのですが……)

2010-03-25 20:51:34.044 FreeJournal[1783:207] *** -[Record release]: message sent to deallocated instance 0x5519010

デバッグ要のユーザーログ出力

デバッグというとVB(というよりもMS-BASIC)などのインタプリタ寄りの言語やデバッグ方法に慣れている人は、ブレークポイントやステップ実行によるデバッグをしてしまいがちですが、オブジェクト指向、マルチスレッドプログラミングなどになるとこうした手法でのデバッグでは太刀打ちできません。

また、モバイルとはいえ一昔前のパソコン並みの性能を持っていますから少し大きめのプロジェクトになると一瞬で相当量の動きをするのでいちいち目で動きを追っていては日が暮れてしまいます。

そこで、定常的にユーザーログを標準出力に表示させる方法が分かり易いのでお勧めです。幾つかの方法がありますが、私が現在用いている方法は以下のコードを(基本的に)全てのメソッドの冒頭に挿入するという方法です。

#if kShouldPrintLog
NSLog(@"@%@ : [%s]", [self class], _cmd);
#endif

これによって、当該のメソッドが実行される際には1行クラス名、メソッド名が出力されるようになります。

メリット:コピー&ペーストで良いのでログ出力の実装が楽。
デメリット:引数が出力されない。
課題:3行必要。Xcode4ではビルド時にWarningとなる(!)

これによってバックトレースが取れない不正終了などでも、ログを見ればどのクラスのどのメソッドでとどめが刺されたかを大体突き止められます。

iPhoneローカルにログメッセージを出力する

複数のメンバー、テスターなどが存在していたり、実機を持ち歩いた状態ででテストを行いたい場合、前項のログを取得できず大変不便な自体となります。

レアな障害を捉えられたのにログもバックトレースも無しでは解析のしようもありません。そこで、標準出力の代わりにiPhoneのローカルにASCIIファイルとしてログを出力し、メールなどで送信できるようにする方法を紹介します。

UIは適当に実装してもらうとして、少なくとも1つのボタン(UIBUtton)を用意します。このボタンの役割は次の通りです。

・ログファイルを削除し、古いログデータをクリアする。
・ログの出力先をローカルファイルに切り替える。

- (BOOL)deleteLogFile {

[self finishLog];
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:[self loggingPath] error:nil]; [self startLog];
return success;

}

このように、ボタンが押されるとログファイルを閉じて、削除し、ログをファイルに出力開始するという仕組みです。

- (void)finishLog {

fflush(stderr);
dup2(dup(STDERR_FILENO),
STDERR_FILENO); close(dup(STDERR_FILENO));

}

- (NSString*)loggingPath {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *logPath = [documentsDirectory stringByAppendingPathComponent:@"console.log"];

return logPath;

}

- (void)startLog {
freopen([[self loggingPath] cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);

}

非常に簡便なコードなのですが、この手の紹介を見たことがありません。
因みに、メール送信画面に引き渡す場合は次のようにすればOKです。

- (void)sendLogByMail {

MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init]; picker.mailComposeDelegate = self;

[picker setSubject:[NSString stringWithFormat:@"%@ - Log", [self appName]]];
NSString *message = [NSString stringWithContentsOfFile:[self loggingPath] encoding:NSUTF8StringEncoding

error:nil];

[picker setMessageBody:message isHTML:NO];
[self.navigationController presentModalViewController:picker animated:YES]; [picker release];

}

 

[作成中]

 

 
お勧めリンク
ダウンロード
ストリーミング関連
Macintosh関連
Windows関連
メディアなど
 
ハードウェア
ソフトウェア/サービス/開発SDK
デジタル一眼レフカメラ
テクニカル記事
趣味関連
ゲーム 〜楽しいゲームの紹介や攻略法
RoverMNI(ローバーミニ) 
雑記
その他
コメント・フィードバック
©1998 CNXGROUP. このページの全部あるいは一部を無断で利用(コピー)することを禁じます。
>