このチュートリアルはiOSチュートリアルメンバーの Matthijs Hollemans 氏によって執筆されました。氏はiOSデベロッパー、デザイナーとして活躍しています。興味のある方はGoogle+ and Twitterへどうぞ。
またお会いしましたね。デバッグチュートリアルです。
前回のチュートリアル ではSIGABRT と EXC_BAD_ACCESSのエラーをXcodeのデバッガとエクセプションブレークポイントを使用して解決する方法を紹介しましたね。
でもこのチュートリアルで使用しているアプリは、それでもまだ何か問題があるようです。なかなか思ったように動いてはくれません。何が潜んでいるんでしょうか?
幸運にも、これらの問題を解決する為のテクニックはまだまだあります。そしてこのシリーズパート2ではそれらのテクニックについて紹介していきましょう。
それでは何はともあれ、このバグ満載のアプリに戻りましょう!
始めましょう: いつ何が起こるのか、そして起こらないのか
Part1から続いて、今アプリは度重なるデバッグワークの結果、何とかクラッシュせずにランすることができるようになりました。しかーし、以下のようにTable Viewには何も表示されていません。
このように期待通りの結果が得られない場合のトラブルシューティングの為に、幾つかのテクニックが存在します。このチュートリアルではまず、NSLog() を使用して問題を解決する方法を見ていきましょう。
Table view controllerのクラスはListViewControllerです。セグエがPerformした後、アプリはListViewControllerをロードしてスクリーンに表示するはずです。この過程はView Controllerのメソッドが実際にコールされているかを確かめることでテストすることができます。それではどこでテストしましょうか?ここはやはりviewDidLoadが適切でしょう。
それではListViewController.mのviewDidLoadに以下のようにNSLog()を追加してください:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"viewDidLoad is called"); } |
つまりアプリをランしてTap Meボタンをタップした後Debag Paneに“viewDidLoad is called”が表示されるはずです。それでは試してみましょう。はい、何にも表示されませんね。この事からListViewControllerクラスが全く使用されていない事がわかります。
つまりこの場合は大抵、Table View Controller シーンの為のListViewCotrollerを使用する旨をStoryboardに伝え忘れている事が原因であると言えます。
ほらね。クラスのIdentity InspectorにはデフォルトのUITableViewControllerが設定されています。これをListViewControllerに変更して、アプリを再びランしてみましょう。今度は“viewDidLoad is called”が表示されましたね:
Problems[18375:f803] You tapped on: > Problems[18375:f803] viewDidLoad is called |
そしてアプリがまたクラッシュしましたね。しかし今度はまた新しい問題のようです。
Note:あなたの書いたコードが期待通りの結果を示さない場合、NSLog()を適切な箇所に使用して、メソッドが適切なタイミングでコールされているかを確認する事は非常に有用です。
Assertion Failures(表明違反)
新しいクラッシュはSIGBARTです。Debag Paneには以下のように表示されています:
Problems[18375:f803] *** Assertion failure in -[UITableView _createPreparedCellForGlobalRow: withIndexPath:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:6072 |
UITableViewに何かしら関係する“assertion failure(表明違反)”ですね。assertion(表明)は何かしらの不具合の発生時にエクセプション(例外)を投げかける、内部の一致をチェックする機能です。このassertionはコードによってチェックする事も可能です。例えば:
- (void)doSomethingWithAString:(NSString *)theString { NSAssert(theString != nil, @"String cannot be nil"); NSAssert([theString length] >= 3, @"String is too short"); . . . } |
上記のコードは引数にNSStringオブジェクトを持たせていますが、nilと3文字以下は渡せないようになっています。つまりこの2つの条件に合わない時は、エクセプションは発生しません。
このassertionをプログラミングの防衛の為のテクニックとして使用する事で、コードが期待通りの動きをするかを常に確認する事が出来ます。加えてassertionは通常はデバッグ時にenableとなり、App Storeへのアプリの申請時に何ら影響はありません。
今回の場合はUITableViewで何かしらが引き金となりassertion failureが発生しています。しかし一体全体何処が問題なんでしょうか? アプリはmain.mで停止され、コールスタックにはフレームワークのメソッドが含まれているだけです。
これらのメソッドの名前から、エラーはTable Viewの再描画に関係があるようです。例えばlayoutSubviewsと_updateVisibleCellsNow:等のメソッド名が見て取れますね。
もっと分かりやすいエラー情報を表示させる為には、アプリを続けてランさせましょう。思い出してください。今アプリはエクセプションが投げかけられる正にその直前で停止しています。Continue Program Executionボタンをクリックするか、もしくは以下をDebag Paneに入力してください:
(lldb) c |
おそらくは2回実行する必要があるかもしれません。コマンド“c”はContinueの略でContinue Program Executionボタンと同じ働きをします。
これでDebag Paneにもっと分かりやすい情報が表示されました:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:' *** First throw call stack: (0x13ba052 0x154bd0a 0x1362a78 0x99a2db 0xaaee3 0xab589 0x96dfd 0xa5851 0x50301 0x13bbe72 0x1d6492d 0x1d6e827 0x1cf4fa7 0x1cf6ea6 0x1cf6580 0x138e9ce 0x1325670 0x12f14f6 0x12f0db4 0x12f0ccb 0x12a3879 0x12a393e 0x11a9b 0x2722 0x2695) terminate called throwing an exception |
オッケー、どうやらヒントになりそうですね。どうやらUITableViewのデータソースがtableView:cellForRowAtIndexPath:から適切なcellを返していないようです。それでは以下のようにListViewController.mのtableView:cellForRowAtIndexPath:にデバッグの為にアウトプットさせましょう:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; NSLog(@"the cell is %@", cell); cell.textLabel.text = [list objectAtIndex:indexPath.row]; return cell; } |
上記ではNSLog()を追加しています。それではアプリを起動させ何がアウトプットされるか見てみましょう。
Problems[18420:f803] the cell is (null) |
オッケー!これはdequeueReusableCellWithIdentifier:をコールしてidentifierとしての“Cell”が見つからない時にのみ表示されます(このアプリはStoryboardでprototype cellを使用しているからです)。
もちろんこれは非常に間の抜けたバグで、もっと早い段階で解決できたでしょう。Xcodeはこのコンパイラ警告“Prototype cells must have have reuse identifiers.”を常時発していたはずなので、ケアレスミスと言えるでしょう。ね、警告を無視してはいけないと以前解説しましたが、それはこういう意味なんです。
それではStoryboardを開いて、prototype cellを選択して、identifierを“Cell”と入力してください:
そうすると、コンパイラ警告が出なくなるはずです。それではアプリを再びランしてみてください。Debag Paneには以下のように表示されるはずです:
Problems[7880:f803] the cell is > Problems[7880:f803] the cell is > Problems[7880:f803] the cell is > Problems[7880:f803] the cell is > Problems[7880:f803] the cell is > Problems[7880:f803] the cell is > |
仮説の証明
NSLog()は生成された6つのtable view cellを表示しています。でも実際にはTableにはまだ何も表示されていません。これはどういう事なんでしょうか? これはですね、シミュレータを適当にタップすると分かりますが、最初の6つのCellが選択されます。どうやらただ単にCellが空っぽなだけのようですね:
それではもう少し深く探ってみましょう: 以下のようにNSLog()を変更して下さい:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; cell.textLabel.text = [list objectAtIndex:indexPath.row]; NSLog(@"the text is %@", [list objectAtIndex:indexPath.row]); return cell; } |
上記はデータモデルの内容をログしています。それではアプリをランして見てみましょう:
Problems[7914:f803] the text is (null) Problems[7914:f803] the text is (null) Problems[7914:f803] the text is (null) Problems[7914:f803] the text is (null) Problems[7914:f803] the text is (null) Problems[7914:f803] the text is (null) |
これで何故Cellに何も表示されないのかが分かりました: 原因はテキストがnilだからです。initWithStyle:のコードをチェックしてみると、間違いなくArrayのlistに文字列が設定されています:
[list addObject:@"One"]; [list addObject:@"Two"]; [list addObject:@"Three"]; [list addObject:@"Four"]; [list addObject:@"Five"]; |
こんな時はテストして確かめてみましょう。Arrayの中に何がリストされているか見てみましょうか。tableView:cellForRowAtIndexPath:の中のNSLog()を以下の様に書き換えて下さい:
NSLog(@"array contents: %@", list); |
これで何かが分かるはずです。アプリをランして下さい。ほら、こんな風に出ましたよ:
Problems[7942:f803] array contents: (null) Problems[7942:f803] array contents: (null) Problems[7942:f803] array contents: (null) Problems[7942:f803] array contents: (null) Problems[7942:f803] array contents: (null) Problems[7942:f803] array contents: (null) |
あはーーん(外人風に)。成る程。もう分かりましたね。Arrayのオブジェクトを最初の段階でallocateし忘れていたんですね。これでは駄目なはずです。これが原因でlistは始めからnilだったと。故にaddObject: と objectAtIndex: が何ら影響を受けていなかった訳です。
つまり、listのオブジェクトはView Controllerがロードされる段階でallocateされる必要がある訳です。それならばとinitWithStyle: で以下の様にメソッドを変更してみましょう:
- (id)initWithStyle:(UITableViewStyle)style { if (self == [super initWithStyle:style]) { list = [NSMutableArray arrayWithCapacity:10]; [list addObject:@"One"]; [list addObject:@"Two"]; [list addObject:@"Three"]; [list addObject:@"Four"]; [list addObject:@"Five"]; } return self; } |
試してみましょう。え?これでも駄目ですかァ?デバッグのアウトプットにはこう出ています:
Problems[7971:f803] array contents: (null) . . . and so on . . . |
こういった場合にはイライラしてしまいがちですが、よーく考えてみて下さい。そもそもinitWithStyle:自体、本当にコールされているんでしょうか?
ブレークポイントを使ってみよう
このような時には、もちろんコード内でNSLog()を使用する事も出来ますが、他にも有用な方法があります: ブレークポイント(Breakpoint)です。エクセプションブレークポイントは既に使用しましたね。ブレークポイントは追加する事が出来るんです。プログラムがその場所に来た時ブレークポイントが引き金となりアプリをデバッガにジャンプさせる事が出来ます。
コード内の行ナンバーをクリックすることでブレークポイントを設定する事が出来ます:
すると青い矢印が表示されブレークポイントが設定された事が分かります。この新しいブレークポイントはBreakpoint Navigatorで確認する事が出来ます:
それではアプリをランさせてみましょう。もし本当にinitWithStyle:がコールされているならアプリは“Tap Me!”ボタンをタップした後、ListViewControllerがロードされた時に停止し、デバッガにジャンプするはずです。
思った通り、何も起こりません。つまりinitWithStyle:はコールされていないのです。それもそのはず、View ControllerはStoryboard(もしくはnib)からロードされているため、initWithStyle:ではなくinitWithCoder:がコールされるんです。
そんな訳で、initWithStyle:を以下に書き換えて下さい:
- (id)initWithCoder:(NSCoder *)aDecoder { if (self == [super initWithCoder:aDecoder]) { list = [NSMutableArray arrayWithCapacity:10]; [list addObject:@"One"]; [list addObject:@"Two"]; [list addObject:@"Three"]; [list addObject:@"Four"]; [list addObject:@"Five"]; } return self; } |
それではブレークポイントはそのままで、試してみましょう:
これでボタンをタップするとアプリはデバッガにジャンプする様になりました:
因にこれはクラッシュではありません。単に設定したブレークポイントの場所で停止しただけです。画面左のコールスタックでは(コールスタックが見当たらない時はDebag Navigatorに切り替えて下さい)buttonTapped:から現時点に移った事が分かります。それより前のメソッドは全てUIKitがsegueをperformさせる為にコールして、新しいView Controllerをロードします。ついでに言えば、ブレークポイントはシステムが内部でどのように動いているかを知る為の非常に有用なツールです。
ここからアプリをランさせるにはContinue Program Executionボタンをタップするかデバッグコンソールに”c”をタイプして下さい。
はい、もちろん又クラッシュしましたね。言ったでしょう?このアプリはバグ満載のアプリなんです!
Note: 続ける前に、とりあえずここではもう必要ないのでinitWithCoder:のブレークポイントを削除するかdisableにしておいて下さい。
ブレークポイントを右クリックしてポップアップメニューからDelete Breakpointを選択して下さい。もしくはブレークポイントをウィンドウからドラッグアウトする事も出来ますし、Breakpoint Navigatorから削除する事も出来ます。
もしブレークポイントをまだ削除したくないなら、単に右クリックのメニューやブレークポイントを1回クリックする事でdisableにする事が出来ます(ブレークポイントの色が薄い青色になったらdisable)。
さらにinitWithCoder:メソッドにはまだよくあるバグが隠れています。分かりますか?
ゾンビ!
それではクラッシュに戻って下さい。はい、EXC_BAD_ACCESSですね。ついでに有り難い事に、どこでクラッシュしたか表示されています。なになに?どうやら今回はtableView:cellForRowAtIndexPathが問題のようです:
EXC_BAD_ACCESS のクラッシュはメモリマネージメント(メモリ管理)が原因です。SIGBARTとは違って、このクラッシュではヒントになるようなエラーメッセージは表示されません。しかーーーし、こんな時に頼れるデバッギングツールがあるんです。はい。それがゾンビ(Zombie)です!
プロジェクトのScheme Editorを開いて下さい:
以下の様にRunを選択してDiagnosticsタブをクリック。Enable Zombie Objectsにチェックを入れます:
それではアプリをランして下さい。アプリは変わらずクラッシュしますね。でも何やら以下のようなエラーメッセージが表示されましたよ:
Problems[18702:f803] *** -[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0x6d84980 |
これがゾンビ(Zombie)有効化の真骨頂です。つまり: オブジェクトを生成するとメモリはヒープにそのオブジェクトのインスタンス変数の為の領域を確保します。そしてオブジェクトがreleaseされ、retainカウントがゼロになるとオブジェクトは破棄されメモリから解放されます。そしてその領域を他のオブジェクトが使用する事が出来る様になります。はい。ここまではいい感じです。
しかし、既に無効となったオブジェクトの領域をポインタで指し示した場合、そこに有効なオブジェクトは存在しないため、アプリはEXC_BAD_ACCESS エラーとなってクラッシュします。
(ここでもしラッキーならクラッシュします。もしアンラッキーなら無効となったオブジェクトをそのまま使用してしまい大混乱が起きるのです。特にどこかで新しいオブジェクトに書き換えられてしまった場合は、非常に困った事になります)。
ここでZombieツールを有効にすると、メモリはオブジェクトをreleaseした時、deallocateしない様にするのです。代わりに、メモリはその領域のアドレスを“undead(アンデッド)”としてマークします。そして後でメモリにアクセスしようとするとアプリは貴方の間違いを“message sent to deallocated instance”エラーとして指摘するのです。
つまり、これこそが今のこの状況です。以下がundead(アンデッド)オブジェクトが存在する行です:
cell.textLabel.text = [list objectAtIndex:indexPath.row]; |
cellやtextLabelオブジェクトはおそらく問題ないでしょう。indexPathも然り。ならば、この“list”がundeadではないかと推測できます。
そしてこの推測を裏付けるヒントが以下のエラーメッセージです:
-[__NSArrayM objectAtIndex:] |
このアンデッドのオブジェクトは_NSArrayMであると表示されています。Cocoaでのプログラムを勉強された方ならお分かりですね。そしてまさしく”list”はNAMutableArrayです。
それでは確認してみましょう。”list”Arrayをallocateした後に以下のNSLog()を追加して下さい:
NSLog(@"list is %p", list); |
するとエラーメッセージと同じメモリアドレスが表示されるはずです(私の場合は0x6d84980と出ています。しかし貴方の場合は違ったメモリアドレスが表示されるかもしれません)。
ここでも別の方法としてデバッガに”list”の変数アドレスを表示させる為、”p”コマンドを使用できます(これとは別に”po”コマンドはオブジェクトを表示させます)。この方法はNSLog()+コンパイルの手間を短縮する為に有効です。
(lldb) p list |
Note: 残念な事に、この方法を私がXcode 4.3で試した場合、何故うまくいきませんでした。アドレスは常に0×00000001を指し示し、おそらくは”class cluster”が原因だと思われます。
GDBデバッガを使用すると、ちゃんと表示されました。しかも”list”がZombieだと教えてくれてもいます。つまりおそらくこれはLLDBのバグなのでしょう。
initWithCoder:の”list”Arrayは今以下の様になっています:
list = [NSMutableArray arrayWithCapacity:10]; |
このプロジェクトはARCを有効化していないため、手動でメモリを管理する必要があります。つまり変数をretainする必要がある訳です:
// in initWithCoder: list = [[NSMutableArray arrayWithCapacity:10] retain]; |
メモリのリークを防ぐ為には、オブジェクトをreleaseする必要があります:
- (void)dealloc { [list release]; [super dealloc]; } |
それでは再びアプリをランして下さい。はい。同じ行でクラッシュしますね。でもデバッガのアウトプットが変わった事に注目して下さい:
Problems[8266:f803] array contents: ( One, Two, Three, Four, Five ) |
これはArrayが適切にallocateされ文字列が含まれている事を示しています。そしてクラッシュが EXC_BAD_ACCESS からSIGABRTに変更されました。はい。1つ解決すると又問題が起きる訳です。これが人生なんですね。乗り越えていきましょう。 :-]
Note:これらのメモリ管理がARCによって大幅に軽減されたとは言え、EXC_BAD_ACCESSえらーのクラッシュは起こります。
ここで一言: EXC_BAD_ACCESSエラーに遭遇した時は、Zombieを有効化してみる事をお勧めします。.
Zombieは非常に有効なツールですが常に有効化しておくべきではありません。何故ならこのツールはメモリをdeallocateせず、アンデッドとしてマークする為、最終的には至る所でメモリリークが起き、メモリがパンクしてしまうからです。故にZombieを有効化する時はメモリ関係のエラーと診断された時に限定しましょう。それ以外は無効化にしておくべきです。
アプリでステップ
それではブレークポイントを使用して新しい問題に立ち向かいましょう。クラッシュが起こった行にブレークポイントを設定して下さい:
アプリを再びランしてボタンをタップして下さい。するとデバッガにジャンプします。tableView:cellForRowAtIndexPath:が呼ばれるのは初めてですね。あ、これはクラッシュではありませんよ。念の為。一旦停止しているだけです。
アプリがどこでクラッシュしたのか特定したい時は、Continue Program Executionボタンをクリックして、(lldb)プロンプトの後に”c”と入力して下さい。これでアプリが停止している箇所からプログラムが開始します。
何も表示されないかもしれませんが、Debug Paneには以下の様に表示されています:
Problems[12540:f803] array contents: ( One, Two, Three, Four, Five ) |
これはtableView:cellForRowAtIndexPath:が1度は何の問題もなく実行された事を意味します。というのもNSLog()はブレークポイントの後に実行されているからです。ですから最初のcellの生成に問題はありません。
もし以下の様にデバッグプロンプトに入力すると:
(lldb) po indexPath |
アウトプットには以下の様に表示されます:
(NSIndexPath *) $3 = 0x06895680 2 indexes [0, 1] |
ここで重要なのは[0,1]です。このNSIndexPathオブジェクトはどうやらセクションが0で行(row)が1を意味しているようです。言い換えるなら、table viewは2番目の行を訪ねています。この事から、1番目の行の生成時には何の問題も無かった事になります。問題のクラッシュ箇所はここではありません。
それではContinue Program Executionボタンを数回押してみてください。するとある特定の箇所でアプリが以下のメッセージとともにクラッシュします:
Problems[12540:f803] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]' *** First throw call stack: . . . and so on . . . |
indexPathオブジェクトを調べてみると:
(lldb) po indexPath (NSIndexPath *) $11 = 0x06a8a6c0 2 indexes [0, 5] |
セクションはまだ0ですね。でも行が5と表示されています。ここでエラーメッセージが“index 5”と表示されている事に注目して下さい。行数の場合0からスタートしますから index 5とは6行目という事になります。しかし、データモデルには5個しかありません。どうやらtable viewは実際の行数よりも行数が多いと勘違いしているようです。
原因はもちろん以下のメソッドにあります:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 6; } |
ここは以下であるべきですね:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [list count]; } |
ブレークポイントを削除するか無効にしてアプリを再びランさせましょう。そして、やっとtable viewが表示され、クラッシュしなくなりました。
Note:“po”コマンドはオブジェクトを調べる時に非常に有用です。プログラムがデバッガで停止した時、ブレークポイントの後やクラッシュの後のどちらの場合にも使えます。ただし、コールスタックでハイライトされている正しいメソッドの確認をする必要があります。それ以外はデバッガが問題の変数を探し当てる事が出来ません。
これらの変数はデバッガの左のpaneでも確認できます。しかし時にはこれらの解読に時間を要する場合もあります。
またしても
えーと、あれ?私、もうクラッシュは無いって言いましたっけ?えーーっと、実はまだ…。行をスワイプして削除してみて下さい。はい。tableView:commitEditingStyle:forRowAtIndexPath:で止まっちゃいましたね。
エラーメッセージは:
Problems[18835:f803] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046 |
どうやらここではUIKitから来ているようですね。コードではありません。それでは”c”と何度か入力してもっと有力なエラーメッセーージ情報を表示させませましょう:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).' *** First throw call stack: . . . |
はい、出ました。これはかなり正確な情報のようですね。この解説ではtable viewは削除されました。しかしデータモデルから削除し忘れているようです。結果として、table viewに何の変化も見られません。ではメソッドを以下に修正しましょう:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [list removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } |
素晴らしい!お疲れさまでした!ここまで紆余曲折ありましたが、何とかクラッシュの無いアプリを作成する事が出来ました。:-]
次にお勧めのチュートリアルを教えて下さい
覚えておくと良い事:
アプリがクラッシュした時、まず始めに行う事は何処で何故クラッシュしたのかを知る事です。この二つが分かっていればクラッシュを修正する事は大抵の場合難しくはありません。そんな時デバッガが効力を発揮します。でもまずはその使い方を学ばなければなりません。
時にはクラッシュは無作為に起こります。こういった時には修正は非常に困難になります。特に複数のスレッドで作成している場合などです。しかしながら、ほとんどの場合は一貫性のあるクラッシュです。
もし最小限のステップでクラッシュを再現できる様になれば、それは同時にバグの修正に非常に有用なスキルとなり得ます。しかしエラーの再現が出来なければ、どのように変更したのかさえ確かではなくなってしまうでしょう。
ヒント:
- もし、アプリのクラッシュがmain.m
- Exception Breakpointを有効にすると、ヒントとなるようなエラーメッセージは表示されないかもしれません。そのような場合には有用なメッセージが表示されるまでアプリを再開するか、もしくはデバッガプロンプトに“po $eax”と入力する事をお勧めします。
- もし、EXC_BAD_ACCESSエラーが出た場合にはZombieを有効にして、再び試してみたください。
- よくあるクラッシュの原因としてはnibやStoryboardの接続ミスがあります。このような場合は通常コンパイラエラーにならなかったり、見えないエラーとなります。
- コンパイラの警告を無視してはいけません。警告がある時は、ほとんどの場合原因があります。何故そのようなコンパイラ警告が出ているのかがはっきりしない場合には、まずは原因を探ってみて下さい。これはライフセーバーとなり得ます。
- デバイスでのデバッグはシミュレータのそれとは多少異なります。この2つの環境は全く同じではないのでそれぞれで違った結果が生じる事があります。例えばこのチュートリアルでの”Probrem”アプリをiPhone 4でランした場合、まず始めに起こったクラッシュはNSArrayの初期化が原因でした。原因はnilの見張りでした。違うView ControllerのsetList:をコールした事が原因ではありませんでした。
もちろん静的なアナライザツールを忘れてはいけません。これはもっとたくさんの間違いを読み取ります。貴方が初心者なら、これを常に有効化しておく事をお勧めします。これはプロジェクトのBuild Settingから設定できます。
それではデバッグを楽しんで下さい! :-]
このチュートリアルはiOSチュートリアルメンバーの Matthijs Hollemans 氏によって執筆されました。氏はiOSデベロッパー、デザイナーとして活躍しています。興味のある方はGoogle+ and Twitterへどうぞ。
アプリがクラッシュしちゃった。さて、どうしましょう- Part 2 is a post from: Ray Wenderlich
The post アプリがクラッシュしちゃった。さて、どうしましょう- Part 2 appeared first on Ray Wenderlich.