Hatena::Groupiphone-dev

iPhoneアプリ開発まっしぐら★ このページをアンテナに追加 RSSフィード

引っ越し後の日記はコチラです

tokoromのその他の日記

2012-07-29ブログ引っ越します

ブログ引っ越します

23:49 | はてなブックマーク -  ブログ引っ越します - iPhoneアプリ開発まっしぐら★

  • Gitで記事の管理/更新をしたい
  • 記事の記述をMarkdownで統一したい

など幾つかの理由により、今後は Github Patges & Octoress でブログを書くことに決めました。

何日か試してみていろいろと都合が良さそうなので、

http://www.tokoro.me/

に引っ越しをします。

今後とも宜しくお願いします!

2012-05-20

vim-quickrun + GHUnit でTDD

| 04:31 | はてなブックマーク -  vim-quickrun + GHUnit でTDD - iPhoneアプリ開発まっしぐら★

VimmerなiOSプログラマの自分にとってはVim上でコーディングからユニットテストの実行/確認までできたら最高です。

ということで、vim-quickrunを使ってVim上でTDDできるようにした記録を残します。

参考記事

あらかじめインストールしておくもの

  • vim-quickrun v0.6.0dev *1
  • GHUnit *2

GHUnitについては

A-Liaison BLOG: Jenkins を iOS アプリ開発に導入してみた (GHUnit編)

が分かりやすいです。

ここを参考にCUIからテストが実行できるところまで設定をしておく必要があります。

VimでTDDするにはこのGHUnitの CUIから特定のテストを実行できる というのが重要になります。

また、CUIでGHUnitのテストを実行した場合の どこがエラーになっているのかが一目でわかりづらい というデメリットもvim-quickrunで解決します。

やったこと

  • vim-quickrunにGHUnit用のoutputterを追加
  • Objective-Cのテストコードでquickrunしたときはその追加したoutputterを使うように設定

実行結果のイメージ

■ テスト失敗

こんなかんじに失敗したテストがレッドで表示されます。

f:id:tokorom:20120521042646p:image

その後、失敗したテストの場所に自動でカーソルが移動します*3

f:id:tokorom:20120521042738p:image

■ テスト成功

こんなかんじにグリーンバーが表示されます。

f:id:tokorom:20120521042737p:image

■ ビルドエラー

おまけで、ビルドエラーのときもエラー箇所に自動でカーソルが移動します。

f:id:tokorom:20120521042739p:image

vim-quickrunに追加したoutputter

上記参考記事とvim-quickrunにはじめから入っているoutputterの quickfix.vim をベースとして以下のようなoutputterを作成しました。

let s:save_cpo = &cpo
set cpo&vim

let s:outputter = quickrun#outputter#buffered#new()
let s:outputter.config = {
\ }

function! s:outputter.finish(session)
  let data = self._result
  lclose
  if stridx(data, '** BUILD SUCCEEDED **') > 0
    " Success
    let message = matchstr(data, 'Executed .*)\.')
    if 0 == strlen(message)
      let message = '** ALL GREEN **'
    endif
    highlight GHUnitSuccess term=reverse ctermbg=darkgreen guibg=darkgreen
    echohl GHUnitSuccess | echo message | echohl None
  else
    " Failed
    try
      if data =~ '\vFile\:[^:]+Line\:'
        set errorformat=%E%.%#File:\ %f,%C%.%#Line:\ %l,%Z%.%#Reason:\ %m
        let message = matchstr(data, '\zsFailed tests.*\zeCommand ')
        if 0 == strlen(message)
          let message = '** FAILED **'
        endif
      else
        set errorformat=%f:%l:%*[^:]:\ %m
        let message = '** BUILD FAILED **'
      endif
      cgetexpr self._result
      cwindow
      cc
      for winnr in range(1, winnr('$'))
        if getwinvar(winnr, '&buftype') ==# 'ghunit'
          call setwinvar(winnr, 'quickfix_title', 'quickrun: ' .
          \   join(a:session.commands, ' && '))
          break
        endif
      endfor
      highlight GHUnitFailed term=reverse ctermbg=darkred guibg=darkred
      echohl GHUnitFailed | echo message | echohl None
    finally
    endtry
  endif
endfunction

function! quickrun#outputter#ghunit#new()
  return deepcopy(s:outputter)
endfunction

let &cpo = s:save_cpo

やっているのは大枠

  • GHUnitの実行結果が テスト成功 / テスト失敗 / ビルドエラー のどのステータスかを判断
  • その実行結果によりerrorformatを差し替え
  • 実行結果の中から表示用メッセージを拾い、テスト成功ならグリーンで、テスト失敗/ビルドエラーならレッドで表示
  • QuickFix

といったことです。

このoutputterはgithub*4に置いてあります。

.vimrc に追加した項目

最後に、これを実行するために.vimrcに追記したのが以下になります。

" ファイル名の最後がTest.mだったら objc.test というファイルタイプにする
autocmd BufWinEnter,BufNewFile *Test.m setfiletype objc.test

" quickrunの設定を初期化
let g:quickrun_config = {}
" quickrunのどのファイルタイプでも適用する設定
let g:quickrun_config['*'] = {'split': 'below'}
" ファイルタイプがobjc.testの場合、make test を実行してその結果をghunit用のoutputterで出力する設定
let g:quickrun_config['objc.test'] = {'command': 'make', 'cmdopt': 'test', 'outputter': 'ghunit'}

" Space, q でquickrunする(これは普段使っている設定にしてください)
silent! map <Space>q <Plug>(quickrun)
" Space, Space, q で今開いているテストケースだけquickrunする(ファイル名がテストクラス名と一致している前提です)
silent! map <expr> <Space><Space>q ':QuickRun -args "TEST=' . expand("%:t:r") . '"<CR>'

RanjeetRanjeet2012/08/08 18:35I tuhoght I'd have to read a book for a discovery like this!

nclhwvvnclhwvv2012/08/08 22:44NwHB8v <a href="http://bmtcwusiedal.com/">bmtcwusiedal</a>

2011-12-25

uncaught exceptionが発生した場所を確認する

| 02:42 | はてなブックマーク -  uncaught exceptionが発生した場所を確認する - iPhoneアプリ開発まっしぐら★

uncaught exception でアプリリセット

uncaught exceptionでmain.mUIApplicationMainのところでリセットが発生しているときに、以下のようなログが出て、

2011-12-26 02:14:41.837 test[64894:f803] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(0x12e2052 0x1896d0a 0x12ce674 0x4a7f7 0x2ab7 0x22d9d6 0x22e8a6 0x23d743 0x23e1f8 0x231aa9 0x21cffa9 0x12b61c5 0x121b022 0x121990a 0x1218db4 0x1218ccb 0x22e2a7 0x22fa9b 0x27d9 0x1fc5)

「で、けっきょくこのExceptionどこで発生してんねん」と思ってしまうことがあります*1

ふつーにぱっと見で認識できるスタックトレースとか出してくれればいいのにねって。

exception をキャッチしてデバッグ表示

それなら、uncaughtなexceptionをキャッチしてあげようということで、UIApplicationMainのところをtry/catchで囲ってデバッグ表示を追加してみました。

int main(int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  int retVal;
  @try {
    retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
  @catch (NSException *exception) {
    NSLog(@"%@", [exception callStackSymbols]); //< ★1
    @throw exception; //< ★2
  }
  @finally {
    [pool release];
  }
  return retVal;
}
★1

どうもiOS4.0から、NSExceptionにcallStackSymbolsという便利メソッドが追加されたようなのでそれを利用。

そうすると、uncaughtなexceptionをキャッチして、以下ようにそのexceptionが発生するまでの経緯がログ出力されます。

2011-12-26 02:27:18.933 test[65600:f803] (
	0   CoreFoundation                      0x012e206e __exceptionPreprocess + 206
	1   libobjc.A.dylib                     0x01896d0a objc_exception_throw + 44
	2   CoreFoundation                      0x012ce674 -[__NSArrayI objectAtIndex:] + 196
	3   test                                0x0004a6e7 -[RootViewController init] + 215
	4   test                                0x000029a7 -[AppDelegate application:didFinishLaunchingWithOptions:] + 567
	5   UIKit                               0x0022d9d6 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1292
	6   UIKit                               0x0022e8a6 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 508
	7   UIKit                               0x0023d743 -[UIApplication handleEvent:withNewEvent:] + 1027
	8   UIKit                               0x0023e1f8 -[UIApplication sendEvent:] + 68
	9   UIKit                               0x00231aa9 _UIApplicationHandleEvent + 8196
	10  GraphicsServices                    0x021cffa9 PurpleEventCallback + 1274
	11  CoreFoundation                      0x012b61c5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
	12  CoreFoundation                      0x0121b022 __CFRunLoopDoSource1 + 146
	13  CoreFoundation                      0x0121990a __CFRunLoopRun + 2218
	14  CoreFoundation                      0x01218db4 CFRunLoopRunSpecific + 212
	15  CoreFoundation                      0x01218ccb CFRunLoopRunInMode + 123
	16  UIKit                               0x0022e2a7 -[UIApplication _run] + 576
	17  UIKit                               0x0022fa9b UIApplicationMain + 1175
	18  test                                0x0000258b main + 187
	19  test                                0x00001d65 start + 53
)

これを見れば「RootViewControllerのinitメソッド内にダメなコードがあるんだね」ということがすぐに分かるようになります。

★2

ちなみにexceptionをキャッチしっぱなしだとアプリ的には異常がなかったという扱いになってしまうので、同じexceptionをそのままthrowしておきます。

DEBUGビルド時にだけこれを有効に

DEBUGビルド時にだけこれを有効にしたい場合には、以下のように「#ifdef DEBUG」で囲ってやるのが良いかもしれません。

int main(int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  int retVal;
#ifdef DEBUG
  @try {
#endif
    retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
#ifdef DEBUG
  }
  @catch (NSException *exception) {
    NSLog(@"%@", [exception callStackSymbols]);
    @throw exception;
  }
  @finally {
#endif
    [pool release];
#ifdef DEBUG
  }
#endif
  return retVal;
}

ARCを利用する場合

ARCを利用する場合はもうちょっとシンプルにできる。

int main(int argc, char *argv[])
{
  int retVal;
  @autoreleasepool {
#ifdef DEBUG
    @try {
#endif
      retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
#ifdef DEBUG
    }
    @catch (NSException *exception) {
      NSLog( @"%@", [exception callStackSymbols] );
      @throw exception;
    }
#endif
  }
  return retVal;
}

でも、こんなごちゃごちゃ書かなくてもよいかもしれない

ちなみに、uncaught exceptionを制御するための「NSSetUncaughtExceptionHandler」といった関数もあるようで、こちらを使えばもっとスマートな方法があるかもしれません*2

もう眠くなったのと、↑でも特に困らなかったので今日はここまで。

*1:そもそもこのExceptionの発生場所をすぐに特定できる方法を既知のかたは是非フォローお願いしますm(_ _)m

*2:こちらも既知のかた是非フォローをお願いします

TakuTaku2016/05/10 16:26参考になりました。

でも、全てのエラーをキャッチしないみたいですね。
残念ながらNSSetUncaughtExceptionHandlerも全てのエラーをキャッチしないみたいですね
例えば、
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self performSelector:@selector(nonExistantMethod) withObject:nil afterDelay:4.0];
}
となるとキャッチされますがアプリケーションもcrashしますね
-[UncaughtExceptionsAppDelegate nonExistantMethod]: unrecognized selector sent to instance」

2011-11-27

MacアプリでもUIKitを使ってみる with Chameleon

| 10:42 | はてなブックマーク -  MacアプリでもUIKitを使ってみる with Chameleon - iPhoneアプリ開発まっしぐら★

Chameleon

Chameleon - UIKit for Mac development

このChameleonを使うとMacアプリの開発でもUIKitを使えるようになるとのこと。

前々から気になっていたので少し試してみた。

Chameleon自体はBigZaphod/Chameleon - GitHubからダウンロード可能。

また、Chameleonを試用した本記事のソースコード一式はtokorom/ChameleonUIKitDemo - GitHubからダウンロード可能。

今回は、UIViewControllerで画面を1つ作り、そこにUIImageViewやUIButtonを貼りつけて動かしてみるところまで実験。


まずはプロジェクトにChameleonを取り込む

※上記サイトからあらかじめChameleonをダウンロードしておくこと

まずはXcodeで空のMacアプリのプロジェクトを作成する。

そのプロジェクトにChameleonの中のUIKit.xcodeprojだけ追加する。

次に、Build PhasesのLink Binary With LibrariesUIKit.frameworkを追加する。

↓このあたりの実施後の画面が↓になります↓

f:id:tokorom:20111128103910p:image


UIKitを他のコードから使えるようにimport

■ChameleonUIKitDemo-Prefix.pch

プロジェクトの中にはじめからあるプリコンパイル済みヘッダで、上記のように

UIKit/UIKit.h

UIKit/UIKitView.h

をimportしておく。

もちろん、各ソースコードから個別にimportしても構わない。


UIKit用のAppDelegateを作っておく

■ChameleonAppDelegate.h

■ChameleonAppDelegate.m


ここは普段のiOS用アプリ開発とほぼ同じでOK!

唯一違うのは、windowやViewControllerのautoresizingMaskを明示的に指定するコードを追加するくらい。

UIViewControllerがそのまま使えるというのは素晴らしい!


はじめに表示するRootViewControllerを作っておく

ここはiOS用アプリ開発と全く同じ!

ここではUIImageViewとUIButtonを追加している。

※必要な画像はあらかじめプロジェクトに追加しておくこと

■RootViewController.h

■RootViewController.m


Mac用のAppDelegateをUIKit用AppDelegateに接続

■AppDelegate.h

■AppDelegate.m


ここでは、UIKitViewと先ほど作成したChameleonAppDelegate(UIKit用のAppDelegate)を追加する。

UIKitViewは後ほどInterface Builderで接続するためIBOutletを付けておく。

そして、applicationDidFinishLaunchingの中で、UIKit用AppDelegateに移管するため、UIKitViewのインスタンスに対してlaunchApplicationWithDelegateをコールして引数にUIKit用AppDelegateのインスタンスを指定する。


Interface Builder で最後の仕上げ

f:id:tokorom:20111128104037p:image

最後に、Interface BuilderでMainMenu.xibを開き、WindowsのメインViewにUIKitViewを追加する。

UIKitViewというオブジェクトはツール上は存在しないので、代わりにCustom Viewというのを貼り付ける。

そして、そのClass名をUIKitViewに変更する。

※このあたりは↑のスクリーンキャプチャのとおりです


これができたら、そのUIKitViewをApp Delegateに作っておいたOutlet(chameleonView)に接続する。

以上で完了。


ビルド&ラン!

f:id:tokorom:20111128103636p:image

実行してみたところUIImageViewUIButtonもきっちり動き、ボタン押下のアクションも問題なくハンドリングされていました。

なかなか面白いですね!

UIKit.frameworkをappの中に含める設定

なお、このままだとappとして出力した後にUIKit.frameworkが見つからなくて動作しなくなってしまう。

そのため、Builde PhasesCopy Filesという項を追加し、そこにUIKit.frameworkを設定しておく必要がある。

f:id:tokorom:20111130030943p:image

2011-07-09

VimでのiOSアプリ開発に便利なsnippetsファイル作りました

01:48 | はてなブックマーク -  VimでのiOSアプリ開発に便利なsnippetsファイル作りました - iPhoneアプリ開発まっしぐら★

VimでのiOSアプリ開発生活を快適にするために、

  • Foundation.framework
  • UIKit.framework

のクラス、メソッドなどをヘッダーファイル*1から抜いてVim用のsnippetsファイルを作成しました。


snipMate.vim や neocomplcache で利用できる見込み*2です。

もし必要なかたがいらっしゃいましたら

からダウンロードをお願いします。


例えば、pushViewController:animated: のsnippetは、

snippet pushViewController:animated:
	pushViewController:${1:(UIViewController*)viewController} animated:${2:(BOOL)animated}

のように定義されており、どのメソッドも引数のところがプレースホルダになっています*3


なお、スニペット名をメソッドの名前そのままにして長いので、neocomplcasheによるスニペット補完の利用が推奨されます*4


以下、自分用メモ。


各フレームワークのtagsの作成

例えば、Foundation.frameworkとUIKit.frameworkのtagsを作成する場合、

$ ctags -R --language-force=ObjC /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System/Library/Frameworks/Foundation.framework/Headers /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System/Library/Frameworks/UIKit.framework/Headers

とする。

ObjCに対応したctagsを利用する必要あり。

他のフレームワークのやつも欲しければ、ヘッダーファイルが入っているフォルダのパスを加えればOK。


tagsからsnippetsファイルを作成

今回は、rubyでスクリプトを書いて対応。

  • tags2vimsnippets.rb
inputFileName = ARGV[0]
outputFileName = ARGV[1]

result = []
snippets = []
IO.foreach( inputFileName ) {|l|
    result = l.scan(/^[+-]([:a-zA-Z0-9_-]+).*\/\^[+-] \([^)]*\)([^;]+);/)
    if !result.empty?
        next if 6 > result[0][0].length 
        snippets.push( sprintf("snippet %s", result[0][0]) )
        t = result[0][1]
        t = t.gsub(' *', '*').gsub(' (', '(')
        count = 0
        while /([^{][^0-9]+?:)(\(.+?\)[^() ]+)/ =~ t
            count = count + 1
            t = t.sub(/([^{][^0-9]+?:)(\(.+?\)[^() ]+)/, sprintf('\1${%d:\2}', count))
            break if 64 < count
        end
        snippets.push( sprintf("\t%s", t) )
        next
    end
    result = l.scan(/^([:a-zA-Z0-9_]+)/)
    if !result.empty?
        next if 6 > result[0][0].length 
        snippets.push( sprintf("snippet %s", result[0][0]) )
        snippets.push( sprintf("\t%s", result[0][0]) )
    end
}

snippets.uniq!

out = open( outputFileName, 'w' )
snippets.each {|k|
    out.puts k
}
out.close

このスクリプトに先ほど作ったtagsファイルを渡してやる。

$ ruby tags2vimsnippets.rb tags objc.snippets

これでsnippetsファイル完成。

変な部分があったら正規表現を見直す必要あり。

*1:iOS4.3のSDKのもの

*2:手元ではsnipMate.vimのほうでしかチェックしてません

*3:全部チェックしているわけではないので不備はあるかも

*4:スニペット補完を使うなら生のメソッド名のままのほうが便利だと思います

KalynKalyn2011/09/16 19:19You know what, I'm very much inlcnied to agree.

ppoyzurtppoyzurt2011/09/16 20:49dShnsD <a href="http://lfyzjbluhbqu.com/">lfyzjbluhbqu</a>

hgvcreushgvcreus2011/09/17 21:369XOHOe , [url=http://sggbqokkwiui.com/]sggbqokkwiui[/url], [link=http://yzdkxnyyohny.com/]yzdkxnyyohny[/link], http://yommpsvjyaop.com/

gifkjbjbrygifkjbjbry2011/09/19 00:08HVLjji <a href="http://wwxbkjkabkiw.com/">wwxbkjkabkiw</a>

LungaLunga2013/10/12 18:03That takes us up to the next level. Great pongsit.

EricEric2013/10/13 21:07Thanks for sharing. Your post is a useful <a href="http://intzypm.com">cotniibutron.</a>

LailaLaila2013/10/14 03:10That's a nicely made answer to a chnileaglng question http://eqhdjuhzgv.com [url=http://sopkjtcqst.com]sopkjtcqst[/url] [link=http://qpuhxdv.com]qpuhxdv[/link]

RafaelRafael2013/10/15 17:55Wow! That's a really neat <a href="http://piskvjemlml.com">anwsre!</a>

HarunsHaruns2013/10/16 00:47You get a lot of respect from me for writing these helpful arsltiec. http://ofcuvsr.com [url=http://dohvfkcqk.com]dohvfkcqk[/url] [link=http://novxji.com]novxji[/link]