cappuccinoのコンピタブログ

cappmac.exblog.jp
ブログトップ

[Xcode] Exception Breakpointで例外発生箇所を特定

iOSアプリ開発で良く躓くポイントとして、実行時エラーがありますよね。

なんか分からないけどアプリが落ちる、なんかレスポンスが悪い、とかいろいろ有ると思います。
前者に対してはXcodeのLLDBが、後者に対してはInstrumentsが役立つと思います。

今回は、Xcodeのデバッグで役立った機能があったので、メモとして残そうと思います。

と、その前に、前々回の投稿で、「やりたいことを実現するコード」と「のり付けするための知識」という考えに着目して記事にしていこうみたいな宣言をした割に、前回はその部分が不明瞭だった気がします。

そのため、今後、開発メモとして残す記事は、必ず上記が見えるようにしたいと思います。

  • 今回のやりたいこと「現在地周辺のお店情報を収集したい」

このために、以下のコードを参考に(つまりコピペ)しました。

https://github.com/koogawa/VenueListGPS

このソースはここここで詳細に解説されています。

今回、この記事中の「エラー処理は省略されています」をちゃんと読んでいなかったばかりにドツボにはまりました。

実行すると、必ずアプリが落ちる、というエラーが発生してしまいました。
ただし、発生するタイミングは一定しないため、ネットワーク処理部分に問題が有るのでは、と推測しました。

エラーの内容は「'NSInvalidArgumentException', reason: 'data parameter is nil'」というものです。停止するとUIApplicationMainで止まり、スタックトレースは見れる訳ですが、発生箇所を直感的に突き止めるのはかなり難航しそうな感触です。

まずは、怪しそうな箇所を抜粋してみました(VenueListTableViewController.m)。

- (void)locationManager:(CLLocationManager *)manager

didUpdateToLocation:(CLLocation *)newLocation

fromLocation:(CLLocation *)oldLocation

{

// 緯度・経度取得

CLLocationDegrees latitude = newLocation.coordinate.latitude;

CLLocationDegrees longitude = newLocation.coordinate.longitude;

// APIからベニューリストを取得

NSString *urlString = [NSString stringWithFormat:@"https://api.foursquare.com/v2/venues/search?ll=%f,%f&limit=30&client_id=ICIWPLPZATTTPYV0YBSVB4AQCF2PVXUWKHS3ZT1BURV0PS02&client_secret=T5SEMJSHYURT5UGERXLZNCUGI1QZ1JJHWBYN2XLDWK3FQUFN&v=20140629", latitude, longitude];

//NSLog(@"urlString = %@", urlString);

NSURL *url = [NSURL URLWithString:urlString];

NSString *response = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

NSData *jsonData = [response dataUsingEncoding:NSUTF32BigEndianStringEncoding];

NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];

// エラーコードをログに出力

NSInteger errorCode = [[[jsonDic objectForKey:@"meta"] objectForKey:@"code"] integerValue];

NSLog(@"errorCode = %d", errorCode);

// 結果取得

NSArray *venues = [[jsonDic objectForKey:@"response"] objectForKey:@"venues"];

venues_ = [venues mutableCopy];

[self.tableView reloadData];

}


JSONが絡む処理で問題が起きる模様であるとGoogle先生の結果から推測できたので、

NSDictionaryのエラー処理をちゃんとしてみました。


NSError *error;


NSDictionary *jsonDic = [NSJSONSerialization

JSONObjectWithData:jsonData

options:kNilOptions

error:&error];


さらに、NSArrayの取り出しは、nilをチェックし、nilじゃないときだけ処理を実行するよう修正しました。
つまりこんなかんじ。

if (!error) {

// エラーコードをログに出力

if ([jsonDic count] == 0) {

NSLog(@"don't access it as the index is out of bounds");

return;

}else{

NSInteger errorCode = [[[jsonDic objectForKey:@"meta"] objectForKey:@"code"] integerValue];

NSLog(@"errorCode = %ld", (long)errorCode);

// 結果取得

NSArray *venues = [[jsonDic objectForKey:@"response"] objectForKey:@"venues"];

venues_ = [venues mutableCopy];

}

   }else{

NSLog(@"Error: %@", [error localizedDescription]);

}



結果は変わらず・・・

ふと、UIApplicationMainで止まってしまう点についてGoogleで調べていたときに効果的にデバッグするための情報を知りました。(もう return UIApplicationMain で止まっても困らない! Xcodeでのデバッグ方法:Zero4Racer PRO Developer's Blog
これを使うと例外発生時の実行コードでブレークポイントを自動で打って停止してくれるというものです。

  • ブレークポイントペインでAdd Exception Breakpointを選択する。
c0064553_11345038.png
  • 対象例外をすべて「All」とする。
    c0064553_11345505.png


試しにやってみたところ、出ました。ブレークポイントがわかりました!
NSDataの処理をした直後のコードで停止していることが分かりました。
基本的なことですが、NSData型のjsonDataを受け取った直後のエラーチェックが抜けていた訳です。

そこで、JSONのパース処理をする部分はNSDataのチェックでnilでない場合に実行するようにしました。
修正後はこのような感じになりました。

if (jsonData == nil) {

NSLog(@"ERROR!");

}else{

NSDictionary *jsonDic = [NSJSONSerialization

JSONObjectWithData:jsonData

options:kNilOptions

error:&error];

if (!error) {

// エラーコードをログに出力

if ([jsonDic count] == 0) {

NSLog(@"don't access it as the index is out of bounds");

return;

}else{

NSInteger errorCode = [[[jsonDic objectForKey:@"meta"] objectForKey:@"code"] integerValue];

NSLog(@"errorCode = %ld", (long)errorCode);

// 結果取得

NSArray *venues = [[jsonDic objectForKey:@"response"] objectForKey:@"venues"];

venues_ = [venues mutableCopy];

}

}else{

NSLog(@"Error: %@", [error localizedDescription]);

}


}



早速、NSDataのnilチェック処理を追加して、実行してみたところ、5時間経っても動き続けています。
解決しました。
実行した結果は以下のような感じです。
c0064553_11480243.png

  • 今回ののり付けのための知識「サンプルコードはエラーチェックがないものと心して使う」「実行時エラーはツールで特定する」

[PR]
# by cappuccino_mac | 2014-07-01 06:43 | 開発メモ

ViewDidLoad内の処理をメソッド化する

サンプルコードは単一の機能を説明する上で、簡単化したいため、ViewDidLoad内に直接描かれていることが多いです。
そのため、まとまった機能は部品化していきたいため、メソッド化してしまいましょう。

例として以下のソースコードを変更してみます。

https://www.cocoacontrols.com/controls/lmgeocoder

LMGeocoderという、ジオコーディングを簡単化するコントロールのソースコードです。

この中の、LMGeocoderDemoプロジェクトを開いてみます。

実行すると、映像に合わせて、緯度、経度、ジオコードした結果(住所、道路名)が表示されます。

以下のような感じ。
c0064553_17421606.png
ちなみに映像に対して、文字が縦のままなのはHorizontal専用のレイアウトだからです。
レイアウトの調整もそのうち手を付けていきたいですが、とりあえず今日の本題はそのコードの内容について。

ソースコードは以下のファイルで構成されています。
c0064553_18150996.png
このうち、AppDelegate.mとLMViewController.mが、このコードの主要部分となります。

[AppDelegate.m]
特にいじりません。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

メソッドの中で初期化処理が行われております。


[LMViewController.m]

- (void)viewDidLoad

メソッドの中でビューのライフサイクルが動作しています。語弊は有りますが、main関数みたいなものです。

この中で位置情報を取得する処理は、部品化されています。


一方、ビデオを表示する処理は、ベタに書かれています。AVFoundationを使った簡潔なコードなので、このまま部品化して呼び出せるようにまとめておこうと思います。


とりあえず、メソッド化してみます。


返却値も、引数も無いので、以下の関数(videoLayer)を作って処理をコピペしました。{}内はviewDidLoad内のコードをそのまま移植しただけです。


- (void)videoLayer

{

BOOL hasCamera = ([[AVCaptureDevice devices] count] > 0);

if (hasCamera) {

AVCaptureSession *session = [[AVCaptureSession alloc] init];

session.sessionPreset = AVCaptureSessionPresetHigh;

AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];

[captureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

[captureVideoPreviewLayer setFrame:self.backgroundImageView.bounds];

[self.backgroundImageView.layer addSublayer:captureVideoPreviewLayer];

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

NSError *error = nil;

AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

[session addInput:input];

[session startRunning];

}

else {

self.backgroundImageView.image = [UIImage imageNamed:@"background"];

}

}


幸い、スコープはメソッド内で閉じているので、変更はいっさい不要でした(ビデオを表示するだけだしね)。

これでメソッドは作成できたので、あとはviewDidLoadで呼び出せるよう、コードを追加します。
自分の中のメソッドを呼び出すだけなので、以下を追加するだけです。ドットでもかけますが、Objective-Cらしくメッセージで。

[self videoLayer];


この部品は後々、ビューの中で表示するビデオを実現するコードとして利用したいと思います。

今回は改変しても壊れないこと、および見通しを良くすることに主眼を置いたので、自分のやりきった感は少ないですが、実行結果は映像があったり、位置情報が変化したりと見栄えがするので面白いと思います。

今後も、見た目重視のアプリをいろいろいじっていこうと思います。

[PR]
# by cappuccino_mac | 2014-06-29 17:06 | 開発メモ

コピペで作るiOS App

iOSアプリの開発情報は巷に溢れておりますが、大抵、以下のパターンが多いと思います。

  • 言語仕様から理解して、コツコツ作っていく(座学的イメージ)
  • 簡単なアプリを拡張して、実用的なアプリに仕立てていく(実践的イメージ)
  • Tipsのまとめ(「分かる人」向けの開発情報)

どれも必要な情報があって、便利なのですが、手早く作る上で必要な技術とは何か、というのを模索しながら作っていくと、

(やりたいことを実現できるコードのコピペ) + (コピペをのり付けするための知識)

ということが大事かと思えてきました。

コピペで参考になるサイトはいろいろ有りますが、GitHubでパブリックに公開されているコードがよいと思います。
部品化が徹底されているものが多く、殆ど手直しせずに利用できます。

コピペをのり付けする知識は、いくらでもありますが、巷のコードを利用する上で特に必要となるのは、

  • IB依存コードとIB非依存コードの違いを吸収する
  • ビューコントローラとビューの違いを吸収する
  • Storyboardを理解する

だと思います。

この辺りを意識してアプリを作っていく記録を残していきます。

お断り:実践の記録なので挫折しているところも記録していくつもりです。




[PR]
# by cappuccino_mac | 2014-06-28 17:00 | 開発メモ

dp2 Quattroインプレッション

フォトヨドバシより

dp2が発売されたようですね。

画質は素晴らしいの一言。

ぜひ買いたいですが、35mmはDP2 merrillで当分フォローできるので、dp1 quattroを狙いたいところ。
さすがに10万は手が出ないので、1年待ちで8万切りあたりを狙いたいかな。。。シグマがなんかとんでもないキャンペーンをしてくれたら話は別ですがw


[PR]
# by cappuccino_mac | 2014-06-22 22:27

SSDが死んだ

Macに外付けディスクとして、Lacie Little Big Disk Thunderbolt SSD 240GBを接続して
運用していたのですが、数日前から勝手にアンマウントすることが多発し、おかしいなと思
っていたところで、完全にマウントされなくなりました。

ディスクユーティリティを開くと、RAID0のセットのうち、片方のディスクがエラーを起こして
オフライン状態になっていることが分かりました。
このディスクにはiPhotoやiTunesのライブラリが保存されていて(つまり内蔵HDDにはいっさい、
iTunesやiPhotoのデータは無し)、依存度が高いため、バックアップはそれなりに取っていました。
(といっても、iPhotoのバックアップはたま〜にしかとって無かったので、結構焦りましたが...)

そのため、RAIDセットを破棄して、それぞれのディスクをフォーマットし直しました。

いまは問題なく使えているようです。SSD自体が堅牢でもRAID0はエラーを起こすととたんに使え
なくなり、対処不能となることが分かりました。
世の中にはたまに速度向上をウリにSSDのRAID0構成で出荷するマシンがありますが、意外に
脆いかもしれません。

また、TimeMachineでは内蔵HDDのバックアップはしっかり取っていましたが、外付けディスクの
バックアップは手動だったので、今後はTimeMachineに外付けディスクも含めて、バックアップを
取るよう対策しようと思います。

[PR]
# by cappuccino_mac | 2014-06-14 15:53

jetson tk1が来ました

c0064553_21513990.jpg

c0064553_21513927.jpg


取り急ぎ写真を載せてみました。
脚のストックを探さないと…
[PR]
# by cappuccino_mac | 2014-06-01 21:49

Jetson TK1注文してみた

ASCIIより

NVIDIAのTegra K1搭載開発者向けボード「Jetson TK1」がオリオスペックから発売開始されたようです。

早速、ここで注文をしてみました。

このボードはいくつか契約事項があるため、署名が必要となります。
注文後に添付のPDFに署名捺印をしてから発注処理に入るというメールが来ました。

早速、署名捺印をし、郵送しました。

発送は5月下旬〜6月上旬になるみたいです。

Streaming Processor数は192コアなので、絶対性能からいうとけして高い性能ではありませんが、モバイルサイズとしては破格の性能なので、面白そうです。
同梱、もしくはCUDA Registered Developer Programにログインしてダウンロードできることで入手できる(と思われる)Vision Worksがどのようなものかも興味深いです。
[PR]
# by cappuccino_mac | 2014-05-02 21:11 | 雑感

春イベ攻略状況

いまだにE-2あたりをさまよっていますが、ボスを初回撃破記念に投稿。

霧島が虎の子の46cm三連装砲と一緒に海の藻くずとなって消えてしまった反省を生かして、ダメコン
は全員に詰んでおくという対策を施しました。これまでオーバーキル気味だったので火力を下げてでも生存性を上げる作戦です。

とてもフルボッコですが、ダメコンを全員に詰んでいるのでそれほど絶望感は無かったかな。

後半の夜戦とボスは復縦陣で行くと単縦陣に比べてダメージが少なめだった(多分)。

c0064553_14285635.png


一周したあとの消費資源はこんな感じ。

c0064553_14243575.png


ボス撃破でS勝利できた訳ですが、なかなか手に入らなかった阿武隈さんをドロップしました。
あいかわらずレア駆逐艦は手に入らないっすねー。
ドロップ入手画面撮り忘れたので、とりあえず、図鑑画面。

c0064553_1430127.png

[PR]
# by cappuccino_mac | 2014-05-02 14:30

レア艦建造祭り状態?

こちらの熊野
建造レシピを参考にしたら一発で鈴谷が出ました。航空巡洋艦をそろえるクエストで欲しかったので、
とてもラッキー。

一応レシピだけ抜粋すると 400/100/600/30

c0064553_638346.png



調子に乗って、こちらの大鳳建造レシピ(3600/2000/5300/5200/20)を入れたら一発で大鳳さん
がきました。ここのサイトは大和の推定率も比較的高めみたいなので、資源がメド着いたら試してみようと思います。

c0064553_6383175.png



正規/装甲空母勢はこれですべてそろいました。空母でそろってないのは瑞鳳くらいか。
ドロップも建造もやたら空母ばかりがコレクションされていくので、そろそろレア駆逐艦こないかな。

現状、雪風も島風もないです。もちろん、初風、枚風とかもない。強いていえば巻雲くらい?
これもレアリティでいえば普通くらいだと思いますしね。ま、何事も焦らずデイリー回しでいきましょう。
[PR]
# by cappuccino_mac | 2014-04-27 06:40 | 雑感

艦これ5万ポイント分ゲットを試してみた件について その後

結論

だめでした。

何の音沙汰もないので、結局ガセっぽいです。

その後、関係ないキャンペーンメールが続々スパム的に届きますが、
リンクを飛んでも更なるサイト追加のページに飛ぶだけであまり意味は
なさそう。

いわゆるムシのいい話はやはりいいこと無いですね。
[PR]
# by cappuccino_mac | 2014-03-16 15:45