2012年1月15日日曜日

gdb で void* 型の変数をデバッグする

C言語で実装されたライブラリやアプリケーションでは、汎用的な型として随所で void* が使用されますが、これをgdbからデバッグすると、そのままでは型情報が無いためタダのポインタとして扱われてしまいます。これではデバッグ時の都合がよろしくないです。
(gdb) print 0xfee65c0
$1 = 267281856
(gdb) print (void *)0xfee65c0
$2 = (void *) 0xfee65c0
こんなとき、この void* が指し示している先の型がわかりきっている場合は、その型でキャストしてやって:
(gdb) print (struct imap_session_state_data *)0xfee65c0
$4 = (struct imap_session_state_data *) 0xfee65c0
(gdb) print $4
$5 = (struct imap_session_state_data *) 0xfee65c0
参照先にアクセスすればきちんと中身が見えます:
(gdb) print * $4
$6 = {
  imap_session = 0xfee6560,
  imap_mailbox = 0xfee6230 "INBOX",
  imap_flags_store = 0xfee6490,
  imap_ssl_callback = 0,
  imap_ssl_cb_data = 0x0
}
(gdb) print $6->imap_session
$7 = (mailimap *) 0xfee6560
(gdb) print * $7
$8 = {
  imap_response = 0xfee6090 "FETCH completed",
  imap_stream = 0xfeecc90,
  imap_progr_rate = 0,
  imap_progr_fun = 0,
  imap_stream_buffer = 0xfee6a00,
  imap_response_buffer = 0xfee6a20,
  imap_state = 3,
  imap_tag = 4,
  imap_connection_info = 0xfee64d0,
  imap_selection_info = 0xfee6030,
  imap_response_info = 0xfee60e0,
  imap_sasl = {
    sasl_conn = 0x0,
    sasl_server_fqdn = 0x0,
    sasl_login = 0x0,
    sasl_auth_name = 0x0,
    sasl_password = 0x0,
    sasl_realm = 0x0,
    sasl_secret = 0x0
  },
  imap_idle_timestamp = 0,
  imap_idle_maxdelay = 1740,
  imap_body_progress_fun = 0,
  imap_items_progress_fun = 0,
  imap_progress_context = 0x0
}
これでデバッグがはかどりました。

2012年1月2日月曜日

2011年のふりかえりなど

あけましておめでとうございます。年も開けましたので、2011年のふりかえりをやってみて、2012年の抱負を考えてみたいと思います。

■やってみたこと
2010年の途中からiOSアプリの開発担当になったのですが、2011年は始めて一年中iOSアプリの開発に携わることができました。ということでダイジェスト。
  • 1月は前年度から引き続きアプリの修正案件を行なってました。ずいぶんひどく炎上した一年のスタートになったのですが、炎上したプロジェクトでしか学べないものというのはたくさんあるものだと痛感させられました。主に案件がどうして燃えるのかとか、何が死亡フラグか、など。自分一人ではどうにもならないので、急遽助っ人に助けてもらいましてなんとか収拾。助っ人の方々、あの時は本当にありがとうございました><
  • 2月ぐらいからBPRという自社開発のフレームワークとそれを使ったアプリの開発などをしていました。外部に公開するライブラリを組むのは初めてということで、実に勉強になりました。中身の実装もなかなかうまくいったと思ってます。
  • その後ちょっとした案件をこなしてましたが、ここでは複数案件の並行進行を余儀なくされたため、またも自分一人ではどうしようもならない事態に。うまい具合にアルバイトの人に仕事をお願いしたりする必要に迫られるなどしました。
  • 5月〜6月が今年一番の正念場でした。それぐらい難しい案件と他の案件を並行で進めていたのですが、ずいぶん自分の設計からミスをしてしまい、自分の限界を知ることになった気がします。主にCore DataまわりとAPI通信実装まわりの限界がこの案件ではっきりと分かりました。それだけではなく、グループで仕事するときの死亡フラグとか、炎上案件の燃え方とか、これまた大いに学ばされました。
  • 7月からはずっと一本のアプリに集中して新規実装および修正を行なっていました。これまでの案件と打って変わってあまりにもサクサク進んだもので、受託開発での「お客さんの力量」の大事さを実感。むしろ自分のほうがお世話になりっぱなしで申し訳ない気持ちでした。この案件では試しにこれまでの社内ライブラリや設計の基本をすべて捨てて新しい方式でやってみたのですが、大いに成功したところもあればひどく失敗したところもあり、結果としてこの挑戦は正解だったと思っています。またとある理由でopensslのコードを読んだりしたなど、より低レイヤーな部分の知識が限定的ながら得られてきました。
まとめると、これまでにない難易度のアプリの開発に携われて成長できたのと、自分一人ではどうにもならない場面を何度も経験し、仲間の力の偉大さに気付かされたのが今年の収穫だったと思います。

■Keep
去年良かったので続けたかったことはこんなところ。
  • いろんな技術に手を出す。一年以上積み重ねてきたおかげで、ずいぶんと「一番いいやり方」と思われるiOSアプリの開発手法がわかってきたのですが、その方法に固執するとよりよい方法を見落としたり、時代についていけなくなると思ったのであえて別の方法を試し、結果としてよりよいライブラリやプラクティスを学ぶことができたのが実に良かったので、今年もチャレンジしていきたいところです。またClojureをvの人に薦められてやってみて、これまたずいぶんと刺激を受けたので、良いとされる言語やフレームワークに手を出してみてその設計思想を学び取るのは今年もやっていきたいですね。
  • 積極的に仲間に頼る。去年はずいぶんと仲間のみんなに助けて頂きました>< お陰様でずいぶんとスタンドプレーで無理やり解決しようとしてソウルジェムが濁るようなことがなくなったかと思います。今年も自分以外の周りに視野を広げつつ、困ったときに助けていただけるようにもっと人間的に良い人になりたいなーと思ってます><

■Problem
去年よくなかった、またはまだまだ改善の余地があるのはこんなところですかね。
  • 基礎力が足りない。低レイヤーな部分の知識が絶望的に足りない。これはPython温泉の際に指摘されたことで、実に悔しいのですがたしかに通信はHTTPより下のレイヤが殆どわかっておらず、言語はObjective-CがわかってもC/C++がわかってない。アルゴリズムの知識も足りなければ、基本的な数学知識も甘い。
  • さらなる設計力の向上が必要。ずいぶんとよくはなりましたが、それでもまだライブラリやSDKの実装のために必要なレベルの設計力がまだ不足しているように感じられます。
  • グループでの開発手法。自分一人の案件というのがほとんどだったので、たまにチーム戦をやるとひどくミスを連発して、見積もりを間違えたりレビュー不足からひどい炎上を招いたりと失敗が続いているため、グループでの開発のやり方を本気で考えなければならないと痛感させられています。
  • テストの仕方。単体テストと、結合テスト。テストの自動化事態はGHUnitのおかげでずいぶんと進んできたのですが、通信を含んだりファイル操作をするテストが単体テストに紛れ込んでいたりして、自動化の妨げとなっています。SenTestKitとモックを使ったほんとうの意味での単体テストと、それ以外のテストをきちんと分ける開発手法を編み出していきたいです。

■Try
ということでそれを踏まえて、今年はこんな感じのことに挑戦したいです。
  • より低レイヤーな知識の学習。TCP/IPとUDPは必修、できればTCP上でmsgpackあたりのRPCプロトコルを自分で実装できる程度にはなりたいと考えています。CとC++の知識も増やしていきたいです。
  • グループでの開発手法を学びたいです。あとはコミュ力向上。私は自分勝手で人の話を聞かず一度良いと感じたらその手法を信じこんでやまない(人に押し付ける傾向がある)ので、たとえその手法が実際に良かったとしても、チーム全体になじまずマイナスの影響を及ぼしている可能性があります。そういったことをなくすにはどうするか?ということで複数の開発手法を学ぶというのと、人の話を聞いてそれを採用する、これに尽きるでしょう。
  • テストの仕方の改善とCIの導入。まずは単体テストの完全自動化と、リリースビルドの日次生成からはじめ、徐々に取得する集計データの量を増やしていく、例えばカバレッジの計測は簡単に思いつきますし、GoogleやMSはバグが発生しやすい箇所を予め計算する方程式を持っているそうで、そういうのを日次で集計できれば全体の品質に寄与できる可能性があります。

2011年12月7日水曜日

CocoaPods に対応していないライブラリを集めた自分用リポジトリを作る方法

この記事はiOS Advent Calendar 2011の7日目の記事になります。ということでもうすぐクリスマスですね。クリスマスプレゼントの準備はお済みですか?まだの方はちょっとオシャレに、今年のプレゼントをCocoaPodsでご用意してみてはいかがでしょうか?


■ご存じ、ないのですか!?

さて念のためCocoaPodsについておさらい。要するにiOS/OS X用のmavenです。以上。細かい点については以下の記事が詳しいのでそちらをご参照ください。っていうかMac Dev JP Advent CalendarとネタがもろかぶりこのCocoaPodsを使うと今まで大変面倒くさかったライブラリの管理が嘘のように簡単になります。たとえば、新しいプロジェクトを始めるときに、
  • 通信したいからASIHTTPRequestを使おう
  • APIのレスポンスがJSONだからJSONKitも必要だな
  • DBにはCore Dataを採用したいから、MagicalRecordも欲しいな
  • Blocksバリバリ使うからBlocksKitは常識だよね
と思ったら、さくっと以下のような設定ファイル(Podfile, mavenで言うところのpom.xml)を書いてやれば、あとはCocoaPodsが指定されたライブラリを取ってきてビルド設定までやってくれるわけです。
platform :ios

dependency 'ASIHTTPRequest' ,'~> 1.8'
dependency 'JSONKit'        ,'~> 1.4'
dependency 'BlocksKit'
dependency 'MagicalRecord'
ARCあり・なしのライブラリを混ぜても全く問題ありません。素晴らしい!!


■公開なんて、あるわけない

とまぁ実に素晴らしいツールなのですが、問題もあります。
  • つい最近できたばかりのツールなので、対応しているライブラリが少ない
  • 対応しているライブラリでも、もともと依存関係処理をするという文化があまりなかったせいか、一部のライブラリ(Reachabilityとか)が内包された状態で出回っていたり、バージョンタグが一つや二つしか付いていないので上手くバージョン管理が出来ない(しかも極端に古かったりバグがあったり、さんざん)
  • CocoaPods自体が開発途中ということもあり、機能がどんどん追加されているようなのだがドキュメントが追いついていない
例を挙げると以下の画像のような感じで、バージョンが一つしかなかったり、あるんだけれど飛んでいたりなどなど。要するに自分の使いたいコードがCocoaPodsの中央リポジトリで管理されていないということがままあります。



■俺達は、自分たちでpodspecを用意することを......強いられているんだ!

ありがたいことに、CocoaPodsには中央リポジトリ以外の任意のリポジトリをライブラリ管理用のリポジトリとして追加する機能があります。この機能を使って、自分で対応していないライブラリのpodspecファイルを書いて、CocoaPodsで使えるようにすることができます。またCocoaPods 0.3.0以降であれば、設定ファイルに直接自分の好きなライブラリのpodspecを書くこともできるみたいです。

まず最初のステップはpodspecを書くことです。今回は例としてMKNetworkKitというライブラリのバージョンv0.8a用のpodspecを書いてみることにします。

# まずは対象のリポジトリをcloneしてくる
# ここでは相手のリポジトリを直接使ってますが、github上でforkして、そっちを使うようにしてもいいです。forkしたほうが自分で自由にコードに改変を加えたりtagを打ったりできますのでよいかも。
git clone https://github.com/MugunthKumar/MKNetworkKit.git
# 移動
cd MKNetworkKit
# 対象のバージョンにHEADを移動します
git reset --hard v0.8a
# podspecファイルのひな形を出力します
pod spec create MKNetworkKit
これでMKNetworkKit.podspecファイルが出力されますので、今度はこのファイルを書き換えます。先ほどcloneしてきたライブラリのソースコードとプロジェクト設定を見ながら、必要なソースコード、必要なリソース、不要なファイル、ライブラリやフレームワークなどのビルド設定を考えて、適切な設定を用意しなければなりません。
今回はこんな感じで書きました:
Pod::Spec.new do |s|
  s.name     = 'MKNetworkKit'
  s.version  = '0.8a'
  s.license  = 'MIT'
  s.summary  = 'Full ARC based Networking Kit for iOS 4+ devices'
  s.homepage = 'https://github.com/MugunthKumar/MKNetworkKit'
  s.author   = { 'MugunthKumar' => 'mknetworkkit@mk.sg' }
  s.source   = { :git => 'https://github.com/MugunthKumar/MKNetworkKit.git', :tag => 'v0.8a' }

  s.source_files = 'MKNetworkKit/*.{h,m}', 'MKNetworkKit/Categories/*.{h,m}'
  s.clean_paths  = 'MKNetworkKitDemo', '*.xcodeproj', 'sample.JPG'
  s.frameworks   = 'CFNetwork'
  s.requires_arc = true

  s.dependency 'Reachability', '~> 2.0'
end
大事なのはsource, source_files, frameworks, requires_arc, dependencyぐらいです。あとは自分しか使わないならでたらめでかまいません。
sourceは:tagの指定の代わりに:commitでコミットのハッシュ値を指定することもできるみたいです。
このpodspecファイルの記法、やたらとたくさんある上にドキュメントがあまりないので、私は結局公式リポジトリのpodspecを探して見よう見まねで書きました。以下、参考にした物を列挙します。
https://github.com/CocoaPods/Specs
https://github.com/CocoaPods/Specs/blob/master/ASIHTTPRequest/1.8.1/ASIHTTPRequest.podspec
https://github.com/CocoaPods/Specs/blob/master/ASIWebPageRequest/1.8.1/ASIWebPageRequest.podspec
https://github.com/CocoaPods/Specs/blob/master/BlocksKit/0.5.0/BlocksKit.podspec
https://github.com/CocoaPods/Specs/blob/master/BlocksKit/0.9.0/BlocksKit.podspec
https://github.com/CocoaPods/Specs/blob/master/SSToolkit/0.1.2/SSToolkit.podspec
https://github.com/CocoaPods/Specs/blob/master/Kiwi/1.0.0/Kiwi.podspec
https://github.com/CocoaPods/Specs/blob/master/MGSplitViewController/1.0.0/MGSplitViewController.podspec
使用するリソースファイルも指定出来るみたいです。
https://github.com/CocoaPods/Specs/blob/master/SVProgressHUD/0.5/SVProgressHUD.podspec
巨大なのになるとこんなのも書けるみたいです。
https://github.com/CocoaPods/Specs/blob/master/RestKit/0.9.3/RestKit.podspec
https://github.com/CocoaPods/Specs/blob/master/Nimbus/0.9.0/Nimbus.podspec

書き終わったら、書いたpodspecファイルに問題がないかをチェックします。
pod spec lint MKNetowrkKit.podspec
何か問題があれば何かエラーが出ます。修正しましょう。何も無ければ何も出ません。
問題が無くなったらひとまずpodspecファイルについては完成です。次はこのpodspecファイルを置くリポジトリをgithubを使って用意します。github上に適当な名前でリポジトリを作りましょう。私は今回 https://github.com/akisute/Specs というリポジトリを作りました。
リポジトリを作ったら、先ほど作ったpodspecファイルを、以下の命名規則に従ってリポジトリの中に配置します:
/podspecのs.name/podspecのs.version/先ほど作ったpodspecファイル
たとえば今回の例では:
/MKNetworkKit/0.8a/MKNetworkKit.podspec
という名前で配置する必要があります。私が試した際は、間違ってると正しくpodspecファイルを認識してくれませんでした。ファイルを配置したらこのリポジトリをgithubにpushします。

さてこれでpodspec用のリポジトリが出来ましたので、今度はCocoaPods側の設定を行います。以下のコマンドを実行します:

pod repo add myrepo リポジトリのURL
これでmyrepoという名前でリポジトリが登録されます。 ~/.cocoapods/ 以下を覗いてみると、確かに myrepo という名前のリポジトリが追加されているはずです。

あとは普通にCocoaPodsを使うのと同じ要領で、Podfileを書いて、pod installすればうまくいくはずです。・・・といいたいところなのですが、一発でうまくいくことはまれで、たいていpodspecファイルの書き方に問題があったりとか、pod化したい対象のライブラリのコードに問題があってビルドが通らないのが普通です。そこで以下のようなワークフローになります。
  1. コードに問題があるなら、コードをforkして自分の思うように書き換えてpush
  2. podspecファイルを修正して自分のpodspec用リポジトリにpush
  3. 組み込みたいプロジェクトのPodsディレクトリ、Podfile.lockファイル、生成されたxcworkspaceを削除。
  4. 再度 pod install MyProject.xcodeproj を実行。
  5. ビルド。
  6. 問題があれば1. に戻る。
うん、これは素人にはお勧めできない。

しかしながらこのCocoaPodに対応するライブラリが増えていけば、iOSの開発はずいぶんと楽になるはずです。ということで積極的に使っていきたいと思います!