iOSのデバッグを極める

http://www.objc.io/issue-19/

1 comment | 1 point | by WazanovaNews 2年以上前 edited


Jshiike 2年以上前 edited | ▲upvoteする | link

objc.ioはベルリンのメンバを中心に、月替りでiOS関連技術の特定のテーマに絞って発信しているブログ。もう既に知名度はかなり高いかと思いますが、毎月ものすごく力の入った特集ゆえに、その分ボリュームも相当で、読むのも大変というか、時間がないから読めてない人もいるかと。今月は#19としてデバッグの話題です。

  • Peter Steinbergerの「デバッグ : ケーススタディ」では、UIKit上のバグをLLDBで対処した話を紹介。
  • デバッガーでのダンス - LLDBのワルツ」において、Ari GrantはLLDBの使い方を詳説してくれています。
  • DTrace」はiOSシミュレータでしかまだ利用できないようですが、アプリに関して任意の調べたいことを本番のバージョンで、かつアプリを再起動せずに確認できるということで、Daniel Eggertは重宝しているようです。
  • Florian KuglerはiOS8で導入された新機能「アクティビティトレース」を解説。ユーザのアクションなどのイベントが、同期で起きようが他のキュー / プロセスで起きようが、同じアクティビティとしてまとめることができるので、例えば、違うキューで起きているユーザの動作をクラッシュの原因として紐づけることができます。

また、Chris Eidhofは、「デバッグのチェックリスト」と題して注意点をまとめてくれています。

  • コールバックは正しいスレッドにあるか?
    • UIKitオブジェクトをメインスレッド以外からアップデートしてしまうと、うまくいくこともあるが、概ね不思議な振る舞いやクラッシュすることになる。想定しないタイミングでバックグランドのスレッドで起きるコールバックは、ネットワークコールからくるかもしれないし、タイマー / ファイルの読み込み / 外部ライブラリかもしれない。
    • コードにアサーションを置くことで、メインスレッドにいるかどうか確認できる。もしくはスレッドが起きてる箇所をまとめる。例えば、ネットワーク上でAPIのラッパーをつくっているなら、全てのスレッドをそのラッパー内で扱うとか。ネットワークコールは全てバックグランドでされるが、コールバックは全てメインスレッドで起きることが担保される。
  • このオブジェクトは正しいクラスにあるか?
    • Swiftではオブジェクトの型がもっと正確に担保されているので、ほぼObjective-Cにおける問題。
    • フォントに関する新機能を追加したとき、あるオブジェクトがfonts array プロパティを持っていて、それはNSFont型だと思っていたが、実はNSStringオブジェクトということがあった。ほとんどは想定通りに動いていたので、発見が遅れた。
    • isKindOfClass: を使ってアサーションでチェックするか、もしくは、型がわかるかたちの名称にするか。
  • ビルドの環境の違い
    • コンパイラの最適化が、デバッグ時には問題なくても本番でバグになることが、Swiftのリリース後は耳にする。また、開発環境ではアナリティクスのコードをコメントアウトしていたが、本番で反映させると、そもそも当該箇所にミスがあるとバグとして顕在化するというパターンもありうる。
  • デバイスの違い
    • iOSシミュレータだけで確認して実機でテストしてなかったというのは古典的なパターンだが、試す実機の種類が少なくてもバグに出くわすことはある。
    • ビルトインのカメラの利用をする場合は、常に isSourceTypeAvailable: を使って特定のインプとソースが使えるかどうか確認すること。ユーザのデバイスのカメラと自分のデバイスのものとは異なる可能性があるので。
  • ミュータビリティ(変更可能性)
    • 二つのスレッドでオブジェクトを共有し、同時に修正されると、予想しない振る舞いを起こす。この手のバグは再現が難しい。
    • immutableなオブジェクトとして、アクセスしたらその後ステートが変わらないことを担保するという方法がある。
  • Nullについて
    • ほぼ安全にnilにメッセージを送ることができるが、トリッキーなバグが起きることはありえる。但し、Swiftで書いてるときは、以下の問題の多くはSwift optionalsで解決できる。
    • nilパラメーで呼び出すとメソッドがクラッシュすることがある。自分でメソッドを定義するときは、カスタムattributeを付加してnilパラメータを受け入れるかどうかコンパイラに明示すること。もしくはメッセージのフローを逆にする。例えば、NSStringに attributedString: というインスタンスメソッドをもつカスタムカテゴリをつくり、nilにメッセージを送った際にクラッシュでなくnilの結果を取得できるようにする。
    • スカラ値を扱うとき、nilにメッセージを送ると予期しない振る舞いが起きるケースがありうる。
  • クラスにおいて初期化されてないものはないか?
    • initでするのは一般的でないので、オブジェクトを使う前に、オブジェクトにあるメソッドをコールする必要がでてくるケースがある。そのメソッドをコールし忘れると、クラスは完全に初期化されずに、おかしな振る舞いを起こすことがありうる。指示付きの初期化子が実行されたら、クラスは利用できるステートにあることを確認すること。
  • キー値監視
    • オブザーバを追加したが、クリーンアップし忘れるケースはよくある。ReactiveCocoaを使うとよい。もしくは新しいオブザーバを書く際にdeallocに削除する内容を追加することだが、こちらを参考に自動化もできる。
    • 他のプロパティに依存するプロパティを監視する際には、独立したキーを使うこと。さもなくば、プロパティが変更されたときにコールバックを取得できなくなるかもしれない。
  • ビュー
    • Interface Builderを使ってるときに、outletとactionをつなぎ忘れるケース。テストで検知される可能性も高いが、NSAssertなどで確認をする方法もある。
    • Interface Builderにおいて、nibファイルから読み込むオブジェクトグラフが保持されたままであることを確認すること。この点に関しては、Appleの気の利いたガイドを参照されたし。
    • 初期化されてないビューを使おうとしてしまうケースがある。ビューのライフサイクルをよく理解するとバグを減らせる。例えば、既存アプリをiPadに移植する際は、ビューコントローラが子ビューコントローラかどうか、ローテーションの場合はどう対処するかなど細かな違いを考えなくてはいけなくなる。オートレイアウトを利用するとよい。また、制約を加えたビューをスーパービューに追加しようとするのも、よくやってしまうミス。制約事項を反映するにはスーパービューの階層構造の中にビューを置く必要がある。

時間が取れれば、是非原文を読んでみてください。


2014 Topアクセスランキング


Back