Xcodeでデバッグ時に役に立つエラー処理

先日、「Xcodeでリリース用のモジュールを作ってテストする方法」という記事を書きましたが、それが必要な理由を詳しく書いていませんでした。そこで、その理由と合わせてデバッグ時に役に立つエラー処理について書きます。

エラー処理は大きく分けると、次の2つの異なる目的があります。

  1. アプリを使う人に間違った操作をしたことを伝える
  2. 開発者がアプリの不具合を見つけやすくする

今回は、この2つのうちの2番目の項目についての話です。つまり、利用者には伝えたくないけれど、開発者自身がなるべく詳しく不具合の理由を知るための方法です。

方法は2つあります。

  • 不具合の内容を示すログを出力 – NSLog()
  • 想定外の状態になった時にクラッシュさせる – NSAssert()

不具合の内容を示すログを出力

 これはプログラミングの基本でどこでもどんな場合でも通用する方法です。変数にどんな値が入っているのかを確認したい時や、単純に分岐のある部分が処理されるタイミングを確認したい時などにも用います。

ログ出力は、Objective-Cでは次の構文で行います。

NSLog(@"reflesh button pressed!"); //リフレッシュボタンが押されたことを確認
int X;
NSLog(@"Xの値は%d", x); //intの値をログ出力
NSString *name=@"名前”;
NSLog(@"名前は、%@", name); //オブジェクト(文字列)の値をログ出力

このNSLog()が実行されると、デバッグ時にはXcodeのコンソール部分でその出力を見ることができます。

Xcodeのコンソール

そして、これだけでなく、ユーティリティに入っている「コンソール」アプリでもその出力を見ることができます。

つまり、デバッグ用に自分だけで見るつもりのログが、アプリの利用者にも見られてしまうのです。その対策は後述します。

想定外の状態になった時にクラッシュさせる

想定外の状態というのは、文字通り想定外なのでどこに不具合があるのか分かりません。そこで、あるまとまった処理を開始する前に状態を確認し、自分が想定していない状態ならクラッシュさせるコードを埋め込んでおきます。

 例えば、あるメソッドを作っていて、そのメソッドを実行するのに大前提となるオブジェクトがきちんと引き渡っていない時にクラッシュさせる時にこの方法を使います。

 構文はこれです。

NSAssert(condition, desc);
NSAssert1(condition, desc, arg1);
NSAssert2(condition, desc, arg1, arg2);
NSAssert3(condition, desc, arg1, arg2, arg3);
NSAssert4(condition, desc, arg1, arg2, arg3, arg4);
クラッシュ時に出力させたい変数の数ごとに0個から4つまでメソッドが用意されています。
使用例はこんな感じです。
+ (void)removeCDFolder:(CDFolder *)cdfolder inManagedObjectContext:(NSManagedObjectContext *)context
//---------------------------------------------------------------------------
// [概要]CDFolderのレコードを削除する
//          リンクするCDKifuレコードの削除は prepareForDeletionで行う
//---------------------------------------------------------------------------
{
    NSAssert(context != nil, @"CDFolder: context needs to non nil");
    [context deleteObject:cdfolder];
}
この例は、私がCoreDataをいじっている時によくはまったのでその対策をしたコードです。CoreDataのデータをいじる時には、ManagedObjectContextというオブジェクトが必要になるのですが、それををうまく引き継げてなくてnilが渡ってしまい、「なぜかデータが処理されない???」と悩みまくりました。CoreDataのことがよくわかっていないせいもあって、「CoreDataの使い方がまずいのか??」などといろいろ別なことを考えてしまって時間を浪費しました。しかし、結局CoreDataとは全く関係がなくて単純に引数の渡し方がまずかったということが分かって愕然としました。
そこで、このNSAssert()です。
これを使うと、条件(condition部分)に指定した状態にならずにこのNSAssertが実行されるとdesc部分にしていた文字のログを吐いてクラッシュします。それ以来CoreDataをいじるメソッドの先頭には必ずこのNSAssert()を入れています。
変数をログに含めたいときは、NSLogと同じような感じで引数に変数を指定します。
NSAssert2(NO,@"BoadMap:execHand: NO movable piece, hand=%@, AllPieceObjs=%@",hand, self.allPieceObjs);

この例では、引数を2つログ出力させるためにNSAssert2()を使っています。そして、クラッシュさせたい分岐の中で使っているので、必ず実行されるように条件にはNOを指定しています。

リリース用モジュールのビルド設定

NSLog()によるログ出力もNSAssert()によるクラッシュもリリース用のモジュールでは処理させたくありません。そこで、次のように設定を行います。

ターゲットのBuild SettingのPreprocessing

Build Settings > Preprocessing

 Release用のモジュールでNS_BLOCK_ASSERTIONSを指定します。これでリリース用のモジュールではNSAssert()はスルーされ、そこではクラッシュしません。

次に、NSLogの出力をさせないために、次のコードをプロジェクトで使うソースコードのヘッダに埋め込みます。

#ifdef NS_BLOCK_ASSERTIONS
#ifndef NSLog
// NSLogを無効にする
#define NSLog( m, args... )
#endif
#else
#ifndef NSLog
#define NSLog( m, args... ) NSLog( m, ##args )
#endif
#endif

今回の記事は「Mac OS X Cocoaプログラミング 第三版」を参考にしています。この本は、OS X の前身であるNeXT 時代にウォールストリート街に導入されたNeXTでプログラミング業務をしていたアーロン・ヒレガス氏が書いた本です。氏は後にNeXTの従業員になり、NeXTがAppleと合併した時にApple社員となり、現在はBignerdranchという会社を作ってプログラミングを教えています。つまり、Objecive-CとCocoaを昔から仕事で使っていた人が書いた本です。しかも、ウォールストリートで使われるアプリを作っていただけあって、内容は仕事に使うアプリに必要な知識がふんだんに盛り込まれています。仕事でも使えるようなしっかりしたアプリを作りたい方は読んでみて損はないでしょう。現在、和訳されている第三版は、少し古いのでメモリ管理のことについて詳しく書かれています。しかし、Xcode4でARCを使えばその部分は全く必要ないので、読飛ばして大丈夫です。また、Xcode4ではInterface BuilderがXocdeに統合されてしまったので、その部分の解説図が少し違いますが、意味することは同じなのでそんなに戸惑うことはないでしょう。気になる方は、この本より先にStanford大学の講義のLecture 3 まで見るか(参考:アプリの作り方)、英語ですが最新版を買うのがよいでしょう。

この本は、Macアプリを全く作ったことがない方でも、本に書いてあるコードをタイプしてアプリを動かしながら読み進めて行くことができます。かといって、コードばっかりの本というわけでもなく、「こんなに少ないコードでこんなにすごいことができるんだよ!」というお手本を示してくれていて、「おお!こんなことがこんな簡単にできるのか〜!!」と感動しながら読みすすめられます。流石に後半はコードの量が多い部分が増え、内容も難しくなってきますが、差し当たって自分が作りたいアプリに必要の無い項目は読まなくても大丈夫です。これ1冊あればMacアプリの開発のことならなんでもというわけではありませんが、この本が手元にあれば行き詰まった時に解決の糸口を与えてくれることも多いでしょう。


関連記事