2010年12月29日水曜日

はじめての iPhone 開発時にお勧めの本 3 冊

2010/12/29追加: 本を追加したり中身を最新のものにしたりしました。

@monjudoh に社内チャットで
[10/09/17 17:18:12] 文 殊堂: heyあきすて
[10/09/17 17:18:32] 文 殊堂: iPhoneアプリ開発でいい本とかある?
[10/09/17 17:19:42] akisute: yoじゅどうもん
[10/09/17 17:19:43] akisute: たくさんあるぜ
[10/09/17 17:19:52] wozozo ☿: yo
[10/09/17 17:19:53] akisute: なんかみんなから同じ質問されるからブログに書くわ
とか言われたので、お勧め本をまとめてみることにしました。2年前にも似たようなものを書いたような気がしますけど、2年が経過していますから、いろいろと状況も変化していますしね。

4797361786詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ 2010-12-17

by G-Tools


2年前にも詳解しましたがこいつはバイブルです。改訂版になってBlockがらみの記述が増えたり、iOSについての言及が増えて、さらに良書になりました。四の五の言わずに買っておくと良いです。ただし、どちらかというとObjective-Cに焦点を置いている本であり、iOSで何ができるか、ということについてはほとんど記載がないので注意してください。

4797358106はじめてのiPhone3プログラミング
Dave Mark Jeff LaMarche 鮎川 不二雄
ソフトバンククリエイティブ 2009-12-17

by G-Tools


こちらは iOS のバイブルです。初めて挑戦する人に最適ですが、ある程度熟練した人が見ても発見がある良書です。およそ必要なことはすべて得られると思います。

4897978440iPhoneプログラミングUIKit詳解リファレンス
所 友太 京セラコミュニケーションシステム株式会社
リックテレコム 2010-01-12

by G-Tools


UIKitに焦点を置いて書かれている本です。図解が多く、全くiOS開発をしたことがない人でもどのようなUI要素が使えるのか見て学べるため大変おすすめです。

ASIHTTPRequest を使って外部 API から非同期的に結果を取得してみる

ASIHTTPRequest という神の通信ライブラリを使って、うまい具合に外部 API から非同期的に結果を取得・解析して返すようなクラスをつくってみました。以前 NSOperation でやってみたバージョンは こちら。


■主な機能

元々の ASIHTTPRequest にある機能はもちろんご利用いただけます。多すぎて説明し切れませんので、以下の記事を参考にしていただければと思います><

http://d.hatena.ne.jp/ninjinkun/20101122/1290394265
http://macisv.jp/blog/?p=235

さらに今回私が作成した ASIAPIRequest にはこのようなおいしい特典がつきました。
  • POST のパラメータだけではなく、 GET のパラメータも楽々生成してくれるメソッドを用意しました。
  • 非同期実行時の通知方法が, delegate, blockに加え、さらに NSNotification による通知もサポートしました。
  • 非同期的に取得したレスポンスの値をパースするためのコールバックを用意しました。このコールバックメソッドをオーバーライドして、サブクラスで処理を行えば、この中の処理はすべて非同期実行されるため、 XML のパースが遅くて UI が固まったなんてことはもうありません。
  • おまけ的にタグとかつけられるようにしてみました。
その他、お使いになられる際に適当に ASIAPIRequest の中身を書き換え御社のプロジェクトに合うように調整するなどすると面白いと思います。自動的にログインパラメータをつけるようにしたりとか。


■ダウンロード

github にリポジトリを作りましたので、こちらから git でクローンするか、または master のソースコードをダウンロードしてください。

https://github.com/akisute/asi-http-request

タグが付与されていますが、これはクローン元のタグなので、当てにしないでください。常に master の先端をダウンロードするのが一番確実です。

ダウンロードしたら、 Classes ディレクトリと External ディレクトリの中身を適当に自分のプロジェクトにコピーして、プロジェクトに追加していただければOKです。最後に、以下のフレームワークをリンクしてください。
  • CFNetwork
  • SystemConfiguration
  • MobileCoreServices
  • CoreGraphics
  • zlib
ライセンスは元のライブラリに合わせて BSD ライセンスとします。


■使い方

最初に ASIAPIRequest を継承してサブクラスを作成します。
// APIAuthorize.h

@interface APIAuthorize : ASIAPIRequest {
}

// 認証APIのインスタンスを生成する
+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password;

@end
では次にAPIインスタンスを生成するためのクラスメソッドをサブクラスの内部に作ってみましょう。
// APIAuthorize.m

+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password {
NSURL *url = [NSURL URLWithString:@"authorize.json" relativeToURL:API_BASE_URL_STRING];
APIAuthorize *api = [APIAuthorize requestWithURL:url];
api.requestMethod = @"POST";
[api setPostValue:userId forKey:@"userId"];
[api setPostValue:password forKey:@"password"];
api.postRequestFinishedNotificationName = @"APIAuthorizeDidFinishNotification"; // POST 成功時に飛ぶnotificationの名前
api.postRequestFailedNotificationName = @"APIAuthorizeDidFailNotification"; // POST 失敗時に飛ぶnotificationの名前
return api;
}
最後にサブクラス内部でスーパークラスのメソッドをオーバーライドし、通信完了直後に呼び出される処理を記述します。たとえば、レスポンスが返ってきた際に、受け取ったレスポンスをパースして DB に保存したりします。このコールバック内部は UI スレッドとは別のスレッドで並列実行されているので、この中でどれだけ重い処理をしても UI は固まりません。その代わり UI を操作する処理はここでは行わないでください。クラッシュします。
// APIAuthorize.m

- (void)postRequestFinished {
// レスポンスステータスコードが異常系の場合はなにもしない
if (self.responseStatusCode != 200) {
return;
}

// レスポンスをパースしてオブジェクトにし、Core Dataに保存する
// 保存したオブジェクトをuserInfoに格納しておく
User *user = [User managedObjectFromJsonString:[self responseString]
inContext:[AppDelegate appDelegate].managedObjectContext];
[[AppDelegate appDelegate].managedObjectContext save:nil];
self.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
user, @"user",
nil];
}

- (void)postRequestFailedWithError:(NSError *)theError {
// なにもしない
}
tag プロパティを使って、同じAPIでレスポンスの種類を分けることができたりします。
// APIAuthorize.m

enum {
APIAuthorizeResponseTagUser,
APIAuthroizeResponseTagToken,
};

+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password {
NSURL *url = [NSURL URLWithString:@"authorize.json" relativeToURL:API_BASE_URL_STRING];
APIAuthorize *api = [APIAuthorize requestWithURL:url];
api.requestMethod = @"POST";
[api setPostValue:userId forKey:@"userId"];
[api setPostValue:password forKey:@"password"];
api.postRequestFinishedNotificationName = @"APIAuthorizeDidFinishNotification"; // POST 成功時に飛ぶnotificationの名前
api.postRequestFailedNotificationName = @"APIAuthorizeDidFailNotification"; // POST 失敗時に飛ぶnotificationの名前
api.tag = APIAuthorizeResponseTagUser // このAPIのレスポンスはUser型だよーとタグをつけておく
return api;
}

- (void)postRequestFinished {
switch (self.tag) {
case APIAuthorizeResponseTagUser:
// Userオブジェクトを作る
break;
case APIAuthroizeResponseTagToken:
// Tokenオブジェクトを作る
break;
default:
break;
}
}
これで API 本体は完成したので、早速実行してみましょう。以下の4つの方法で実行が可能です。
  1. 同期実行
  2. 非同期実行、 delegate で結果を通知してもらう
  3. 非同期実行、 NSBlock で通信完了後の処理を行う
  4. 非同期実行、 NSNotification で結果を通知してもらう
1, 2, 3 については普通の ASIHTTPRequest と同じですので割愛します。 4 は私が新しく追加した機能で、 NSNotification の仕組みを使って実行完了通知を受け取ることが可能になります。たとえばこんな感じになります。
// 適当に認証とかする画面のViewController.m

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Notification通知を開始する
[[NSNotificationCenter defaultCenter] addObserver:self
name:@"APIAuthorizeDidFinishNotification" // さっき決めた文字列
target:self
action:@selector(apiAuthorizeDidFinish:)
object:nil];
}

- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// Notification通知をオフにする
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (IBAction)startAuthorize {
ASIAPIRequest *api = [APIAuthorize apiWithUserId:self.userIdField.text password:self.passwordField.text];
[api startAsynchronomus];
}

- (void)apiAuthorizeDidFinish:(NSNotification *)notification {
// 認証完了時の処理
}
この通知はメインスレッドから呼び出されるので、自由に UI を操作することが可能です。

delegate と比べて、 NSNotification を使った通知の便利な点は以下の通り。
  • delegate を使う場合には、 delegate オブジェクトがメモリから消える前に delegate の始末を行う必要があるが、 NSNotification を使った場合にはその必要がない。自分を NSNotificationCenter から削除するだけでよいので、 API リクエストを比較的投げっぱなしにできる。
  • delegate とは違い、複数のオブジェクトで同時に通知を受け取ることができる。たとえば API を実行した画面とは全然違う別の画面二つで同時に通知を受け取ったりすることが可能になる。

■余談

この ASIHTTPRequest は本当にすばらしいです。最初にこの通信ライブラリを知ったときは、また良くあるただのちょっと便利なだけな通信ライブラリなんだろうと思い気にもとめなかったのですが、実際にコードを見てびっくりしました。私がほしかった通信ライブラリそのものだったからです。

私は通信ライブラリは NSOperation を継承して作るべきだと考えており、実際以前に試してみたことがありました。それは主に、
  • NSOperationQueue のシングルトンインスタンスが勝手に通信をすべて管理してくれるので、クラスの変数として通信クラスのインスタンスを保持しておかなくても良くなるかもしれない
  • NSOperation には依存関係を指定するメソッドがあるので、これを用いれば自動的に通信 A, B, C を順序通りに実行するなどできるかもしれない
  • NSOperation を継承すれば、将来 Apple の中の人がフレームワークを改善した際にマルチコア化した iPhone の CPU の恩恵を自動的に受けられるかもしれない
という考えがあったからです。そしたら見事にこの ASIHTTPRequest が NSOperation を継承して、しかも何かと問題の多い NSURLConnection を使わず NSStream とソケットを用いて自分で Run Loop を回すというすばらしい実装をしているじゃないですか!こりゃもうかないません。自分でやる必要が丸でなくなってしまいました。しかもそれだけではなく、
  • 現在全体の何%まで読み込みが完了したかを delegate で通知できる
  • 通信完了時に呼び出される delegate method はすべて main thread から呼び出されるので、 UI 操作をしても安全
という、まさにかゆいところに手が届く良さがすべてあります。惚れる。もうおそらく当分の間はこれ以上の通信ライブラリが現れることはないんじゃないかと言い切って良いぐらいすてきです。

アプリのビルド時に CSSMERR_TP_NOT_TRUSTED エラーが発生したときの対処法

http://d.hatena.ne.jp/drill256/20090820/1250752178
http://discussions.apple.com/thread.jspa?threadID=1630090

このエラーは、以下の証明書がすべて存在しないか、または Keychain Access 内でのステータスが、「この証明書は信頼されています。」ではないときに発生するようです。
  • Apple Worldwide Developer Relations Certification Authority
  • iPhone Developer または iPhone Distribution


上記の画像のように、「この証明書は信頼されています。」と緑色のチェックマーク付きで表示されている必要があります。そうでない場合は何らかの問題があります。

対処法は、
  1. まず何はなくともこれらの証明書がすべて存在するか確認する。 Apple Worldwide Developer Relations Certification Authority を忘れているケースが良くあります。
  2. 「この証明書は信頼されています。」になっていない場合には、証明書を選択して、「情報を見る」 -> 「信頼」 -> 「システムデフォルトを使う」 を選択する。「常に信頼する」ではダメです、エラーになります。
証明書をどのキーチェイン項目に入れていても問題はなさそうです。私の場合は WWDR をシステムに、 iPhone Developer をログインに入れていますが正常に動作しています。

2010年11月16日火曜日

iOS で正規表現を使う (3.0, 3.1, 3.2, 4.0)

iPhone / iPad アプリで正規表現を使いたいときはどうするのか調べてみました。


■iOS 4.0以上

NSRegularExpression があるのでそれを使えば万事解決です。


■iOS 3.2

http://blog.livedoor.jp/pnfhy316/archives/277806.html
こちらのブログでご紹介されているとおり、NSString rangeOfString:options: で NSRegularExpressionSearch を指定するのが楽です。

正規表現のグループ参照はできませんけれど、まぁしょうがないですかね。


■iOS 3.1, 3.0

http://d.hatena.ne.jp/KishikawaKatsumi/20081031/1225463896
こちらのブログでご紹介されているとおり、ライブラリを使うのがよいようです。

または最近調べてわかったのですが、 NSPredicate を以下のように使うと正規表現によるマッチングが可能になる模様です。
NSString *path = @"/path/to/img/img10001.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ matches '.*/img[0-9]{5}\\.png'", path];
BOOL matched = [predicate evaluateWithObject:nil];
キモは NSPredicate の format として matches という構文が使えることと、その中で正規表現が使用できるところですかね。実機の iOS 3.X で試したわけではないのですが、 NSPredicate 自体が iOS3.0 から使用可能になっていることと、 NSPredicate format 関連のドキュメントを見ても特に利用制限とかは書いていなかったので、普通に3.0から使えるんじゃないかと思ってます。もしよろしければ動作報告いただけるとうれしいです。


■iOS 2.X

iOS界のIEだと思って忘れましょう。お客さんに「頼むから対応してくれ」と言われても突っぱねる(すでに全体の1%以下ぐらいのはず)のがベストです。

2010年11月4日木曜日

iPhone / iPad でIPアドレスやMACアドレスを取得するクラスを書いてみた

たまには技術ネタを書きます。 iOS で IPアドレスや MACアドレスを取得するためのObjective-Cクラスを書いてみました。
コードはこちら。

https://gist.github.com/662203

ライセンスは記載のとおりMITライセンスです。

参考にしたサイトはこちら。

http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone
http://iphonesdksnippets.com/post/2009/09/07/Get-IP-address-of-iPhone.aspx


■ほんのちょっとだけ説明

BSDのioctl()関数を使いますと、デバイスのIO周りのありとあらゆる情報を取ってくる事ができます。・・・らしいです。それを使ってIPアドレスやMACアドレスを取ってみました。大本のコードは確か StackOverflow あたりにあったのですが、もろにC言語ベタベタだったので使いやすくするためObjective-Cのクラスにしています。といってもまだまだ改善点が山ほどありますので好き勝手に触りまくってください。

っていうか今気づいた、二番目のサイトそのまんまほとんど同じコードじゃないですか>< 別にいまさら作らなくても良かったですね。

2010年10月26日火曜日

ゲームプログラマになりたい中学三年生のためにアドバイスを考えてみた

■発端
  1. 先日誕生日だったので実家に電話した
  2. おかんが出てきた
  3. おかんの友人の奥様の息子さんの話になる
    • なんでやねん
    • 中学3年で進学シーズンです
  4. なんかゲームプログラマーとかになりたいらしい
  5. けど情報科の高校とか嫌で工業高校がいいらしい
  6. あんたプログラマーでしょなんかアドバイスとかないの
という無茶振りを受けたのでこんな記事を書く事になりました。 あれ、又このパターン?


■いきなり結論

ゲームプログラマーですか? もちろんなれます。続けられるのであれば。

なんだか世の中には 一万時間の法則 というものがあるらしくて、一万時間ほど経験を積めば大成できるらしいです。私も身に覚えがちょっとだけあるので、この一万時間というのはあながち間違ってないのではないかと思います。リンク先の lifehacking.jp さんの記事によりますと、
2年:10000 / (2 x 365) = 13.7 時間
5年:10000 / (5 x 365) = 5.4 時間
10年:10000 / (10 x 365) = 2.7 時間
らしいので、こう考えることが出来ます。
一日 13.7 時間、365日、一日も休むこと無くPCの前に座ってプログラムを書いていられる!
おめでとう、2年でゲームプログラマーになれます!むしろイチローにだってなれますよ!
一日 8 時間、一日も休むこと無く学校に通ってプログラムの勉強ができる!
3年間学校に通うとすれば、卒業するまで毎日やってもまだ6割程度。残りは自分で身につけるため、まだまだ勉強を続ける必要があります。
一日 2.7 時間、365日、一日も休むこと無く家に帰ってからプログラムを書いて勉強できる!
それでも10年続ける必要があります。いまあなたが15歳なら、25歳です。5歳のときの自分、覚えてますか?
はい、相当大変だということがおわかりになるかと思います。つまりこれだけの大変なことをどうやって毎日続けるか、というのがゲームプログラマーになるキモのようです。早速次章以降で検証してみましょう。


■でもちょっとまった

ただちょっと思ったのが、多分彼はゲームプログラマーという仕事を勘違いされているんじゃないかと。ゲームプログラマー = ゲームを作れる人、と思われているのではないかと。実際私も中学生ぐらいの頃はそうでした。

しかし現実は ゲームプログラマー = ゲームを作れる人 ではなく、 ゲームプログラマー = ゲームの一部を作れる人 です。まずこれに気づいて、この現実を受け入れることがすごく大事です。

これはものすごく当たり前のことなのですが、たいてい子供時代に夢見ているゲームプログラマー像にはこれが含まれていません。実際、面白いゲームを作るには、
  • むちゃくちゃ面白いゲームのアイディア
  • 超カッコイイグラフィック
    • 2D
    • 3D
  • 爽快な音楽
    • BGM
    • 効果音
  • そしてもちろんプログラミング
と、これだけの要素が必要になってしまうわけです。もちろんアイディアも絵も音楽も借りてきたりフリーの素材を使ったりできますが、すべてがすべてフリーの素材でまかなえるわけではなく(そもそもやってみるとわかるのですが、すべてフリーの素材で作ると、自分が作ったものではないように感じられ気が萎えてしまいます)、どこかはオリジナルでやらなくてはなりません。で、当然自分ひとりでこれだけの要素をまかなえるかというと 普通は無理 なわけです。もちろん 例外 もいらっしゃいますが・・・とにかく、 ゲームは自分ひとりで作るものじゃないんです。

で、それではゲームプログラマーがやるプログラミングってなんなの、という話になりますが、一番地味です。どんな名作ゲームでもプログラマーが褒められることはまずありません。考えてみてください:
  • このゲームは話が面白い
  • このゲームはシステムが斬新
  • このゲームはゲームバランスが絶妙で対戦が面白い
  • このゲームはグラフィックが超綺麗
  • このゲームのBGMはマジ燃える
どれもゲームを褒める言葉なのですが、なんとまぁ、どの褒め言葉も全然プログラマーが褒められてません。

じゃあ何が楽しくてゲームプログラマーをやるのか?

最高の自己満足です。プログラムを書くこと自体が楽しいのです。以上。

さて、どうしてわざわざこのような事を書いたのかといいますと、実は彼がなりたいのはゲームプログラマーではなくてゲームに関わる仕事の何かじゃないかと思ったからなのです。単にゲームが好きで、PCを使ってゲームに関わる仕事がしたいなら、いくらでも他に道があります。絵を書いたり、音楽を作ったり、シナリオを書いたり。いろいろ試してみて、一番自分が好きになったものを極めればいいのです。ですので心あたりがあるのであれば、プログラミングと決めつけずに絵を書いたり音楽を作ったりシナリオを書いたり、ああそれより何より大事、自分以外のゲームに関わる仕事がしたい仲間を探してみたり、いろいろやってみてください。どうすればいいかわからない?私はプログラマーであって絵を書いたり音を作る方法は全然知りませんが、 Google先生が知っています。 思いつく限りGoogle先生に100回でも200回でも聞けばいいのです。一日 2.7 時間、365日、一日も休むこと無くプログラムを書くのに比べたら屁でもないぐらいカンタンですよね?


■それじゃあプログラマーになろう

よし、これだけ脅してもまだ プログラマー最高!! とお考えのHENTAI諸君、ようこそ!それではさっそく10年間続ける方法を考えます。
  1. 楽をして楽しんで毎日続ける
  2. ほんの僅かでもわからないことがあったら即ググれ
  3. エライ人と仲良くなれ
  4. 英語は絶対に意地でも覚えろ
  5. 入門書を読む
まず一番目。私が事あるごとに言ってますが、楽をしましょう。自分に一番あった方法を取って楽をして、そのかわり毎日続けましょう。ゲームプログラマーになりたいのだからまずはゲームプログラミングだろうと思われるかもしれませんが、実はゲームプログラミングはプログラミングの最高峰、最も難易度が高いのです。少なくともWebプログラミングや、最近流行りの iOS / Android でのプログラミングに比べれば、格段に難しく、いきなりここから入ると確実に心がバキバキ折れます。自分はガッツがあって、絶対最後までやり遂げてやると決めて最後までできる人でしたら、このいきなりゲームプログラミングから入ってゲームプログラマーになるのが最短ルートですが、私には到底不可能でした。

そこで最初はまず楽しむことをおすすめします。F1レーサーだって、最初からいきなりF1マシンに乗ったりはしません。最初は普通の車やカートに乗ります。同じように、いきなり難易度激高のC言語をマスターするのではなく、楽をして楽しんで毎日続けられてしかもためになる、そんなプログラミング言語がいいですよね。いくつか私のおすすめをご紹介しましょう。
Processing
本当におすすめです。ぶっちゃけこの言語だけで2Dグラフィックを使った簡単なゲームを作ることが出来ます。いちいち超面倒なrun loopの管理を自分でする必要もありません。そして使っていて面白い! http://akisute.com/2009/12/processing.html とか見ていただければ面白さが伝わるかと!Processingで何か色々作ってみて、遅いとか重いとかもっと難しいことがしたいと思ったら、DirectXとかバリバリ使ったものに挑戦すればよいのです。
Python
これは非常に地味な言語なのですが、プログラムのロジックやオブジェクト指向などを学ぶのに最適だと思ってます。なにより使っていて楽です。もっとも楽すぎてC言語に戻れなくなるかもしれませんが・・・
HSP
これも入門にはよさそうなのですが、使ったことがないのですみません、感想が出せなくて。
さて次、二番目。ほんの僅かでもわからないことがあったら即ググれ。自分で調べる力がないと仕事でプログラムなんぞ書いていられません。しかし、自分で調べるのは大変な作業で心が折れるときもありますし、なによりGoogle先生ですらわからないことはたくさんあります。

そこで三番目。エライ人と仲良くなれ。昔はそんなの絶対不可能でしたが、いまや Mixi Twitter はてな Facebook なんでもござれです。これらのサービスを使ってちょっと調べればHENTAIレベルの神プログラマの日々のつぶやきを簡単に見ることができます。テンション上がりますね。それにひょっとしたらHENTAIたちと仲良くなれるかもしれません。そうすればGoogle先生すら教えてくれない情報をお互いに交換し切磋琢磨することができるようになります。

中学三年生でプログラマーといえばやっぱり tefu 君をフォローするのがいいのかなと。

四番目。英語は絶対に意地でも覚えろ。これは英語が好きとか嫌いとか関係なく必須です。覚えてください。なぜかというとあなたの目の前にあるコンピューター自体が基本的には英語のような言語しか理解出来ないから(日本語もついでに表示できるよっていう程度に思っておけばよいかと)と、そのコンピューターの中身を作っている連中のほとんどが英語でしゃべっているからです。当然文章も全部英語です。今後もこの状態が変わることはまずないでしょう。なので意地でも覚えてください。どうしても嫌ならば、最後の手段。海外のゲームを買ってきて辞書片手に英語字幕のままプレイしてください。私はこれで覚えました。

最後、入門書を読みましょう。ぶっちゃけ私なんぞに聞くよりそのほうが何倍もいいです(これが言いたかった!)。でもどれを買えばいいかって?これが難しい。でも基本的にはGoogle先生に聞くか、エライ人に聞くかですよね?ここではひとつだけ私が最も好きな本を紹介しておきます。

4798021180ゲームプログラマになる前に覚えておきたい技術
平山 尚(株式会社セガ)
秀和システム 2008-11-15
by G-Tools

セガの中の人が書いている本なので大変スパルタですが、非常にためになります。


■ゲームプログラマーに必要なスキル?

いままではとっかかりのところを説明しましたが、うってかわって仕事でゲームを作るにはこれぐらいのスキルが必要だろうというところです。私は本業がゲームプログラマーではないのでなんともですが、これぐらいは知っておかないとなーと思ってます。なので以下の項目は 好き嫌いかかわらず覚えなくてはなりません。
  • プログラム言語系
    • C
    • C++
  • 学問系
    • 数学, 特に幾何学, 平面幾何は最低限必要, 3次元以上のものが分かれば嬉しい
      • ベクトル
      • 行列
      • 三角関数
    • 物理, 特に運動物理
    • 英語
C, C++はおそらく当分の間はゲームプログラミングの主流言語であり続けるので、覚えておいて全く損なしです。というか、メモリ管理を自分でできない言語でゲームのコアエンジンをかくのは絶対不可能なはずなので、将来的にもC, C++でなくてもそれと同等程度の言語が使われるはずです。

OpenGLとかDirectXを含めていないのは、そんな低レベルなところを直接自分で触ることはまずないと踏んでいるからです。行列の概念さえ理解できていればゲームエンジンにお任せできます。

あと数学の授業は真面目に受けておいたほうがいいです。どこの学校の、文系でも理系でも何でもいいので、嘘でもいいのでうけておきましょう。どうせ細かいことまで覚えて居られませんが、キーワードだけ覚えておけば後からGoogle先生に聞くことが出来ます。私は数学をサボっていたせいで、この年にして勉強し直しです><


■まとめ

こんだけ書いておいて何なんですが、私ゲームプログラマじゃないんです!(キリッ

いや、 ゲームっぽいもの なら作ったことあるんですよ一応><

あ、そうだ、もし世の中のゲームプログラマになりたい中学三年生ぐらいのお子さんをお持ちのお母さん方が万が一
何かの間違いでこのページをご覧になってらっしゃいましたら、一言だけお願い申し上げます。どうかあたたかい目で見守ってあげてください。やりたいとお子さんがおっしゃられたことをやらせてあげてください。私は中学生高校生の身にもかかわらず家に行きこもってネトゲばっかりしてたり、山口から一人で新幹線に乗って大阪東京までネット友達の元まで遊びに行ってましたが、それでも両親は反対しませんでした。おかげさまでネトゲから英語を無駄に身につけたり東京に交友関係ができて東京で就職する際に助かったりいろいろいいことがありました。
そのかわり彼女がいないんですが今ではものすごく感謝しておりますので、どうかよろしくお願いします。

おしまい。

2010年10月10日日曜日

iPad 用スタイラス oStylus を試してみた



つい最近販売開始した oStylus という全く新しいタイプのスタイラスを購入して二週間ほど使ってみましたので、レビューを書いてみます。

2011/02/01追記 - 新価格など記載


■そもそも oStylus って何?

http://ostylus.com/
http://japanese.engadget.com/2010/07/14/ostylus/

とあるカナダの会社が作った、全金属製の静電誘導式タッチパネル用(早い話が iPad 用)スタイラスです。最大の特徴はこれまでにない全金属製であるということ(なんと先端まで金属です)と、なにより先端のタッチ部分が Oの字 になっていてタッチ面を見ながら描画ができるという点です。2010/10/10現在はまだ早期生産フェーズということで、限定250本のみ販売。1本75ドルでした。限定モデルにはすべて刻印が彫られており、私のスタイラスは058番です。


■最初に結論

良い点は以下の通り
  • 全金属製のためタッチ感度は抜群(ほぼ指と同じ)
  • タッチペンの滑りが非常に良い、滑らせることを前提にした設計
  • ペン先を見ながら書ける唯一のスタイラス
  • スポンジ式のペンに比べてペン先が頑丈で寿命が長い
悪い点は以下の通り
  • 価格が非常に高い(75ドル)
  • 2010/10/10現在まだ量産されていないため簡単には手に入らない
  • 長くて重い、持ち運びには全く向かない
  • ペン先の自由度が1軸しかないため慣れるまで書きづらい、慣れてもちょっとツラい
  • 余りにも滑りが良く、ペンと紙とは違った感覚のため、字が書きづらい
  • 保護用のビニールが貼ってあるが、それでもiPadの表面にダメージが入る可能性がある
それでは個別に見ていきます。


■外見

全金属製で重さは100gちょっとぐらい、太さは一般的な鉛筆と同じぐらい、長さは日本で一般的に出回っているボールペンなどよりちょっと長い感じです。慣れるまで重くて長く感じます。慣れても大きめのため持ち運びには大変不便です。簡単に携帯できる Pogo Sketch と異なり、 iPad と一緒に持って歩くには何らかの工夫が必要になると思います。

先端の針金みたいなところは一件弱そうに見えますが、頑丈に作られている上に素材も丈夫で、そうそう簡単には破損しません。また金属であるためスポンジタイプのスタイラスと違い摩耗がほとんど発生しません。長持ちします。

Oの字部分の裏にはビニールが貼られており、そのおかげで iPad の表面に傷を付けないですみます。また滑りも良いです。野外広告用のビニールだそうで、耐久性も抜群だとか。このビニールは本体とは別に2つ添付されているので、万が一はげてしまっても安心です。

実際に二週間ほど書いてみたところ、目立つようなキズは確かに発生しませんでしたが、表面に貼ってある保護シート(エレコム製)にほんのわずかながら擦り傷のような後が残りました。保護シートをに貼り替えてみたところいまのところそのようなキズは発生しておりません。保護シートの質によってダメージが発生するのかもしれません。保護シート無しで直接本体に触れさせたときのダメージは今のところ不明です。言われなければ気づかないようなレベルですが、それでも気になる方は oStylus の使用を避けた方が無難です。


■動作

動作についてはこちらの公式ビデオを見るのが一番です。


簡単に補足しますと、まず形状が独特であるため、使い慣れるまで非常に苦労します。長くて重いですし、先端は自由度が1軸しかないため Pogo Sketch のペン先に比べてペンと同じように書きづらいです。しかしながらひとたび慣れてしまうと、このスタイラスのメリットがどんどん生きてきます。金属製だけあってタッチ感度は群を抜いてダントツですし、ペン先のすべりも表面のビニールのおかげか非常に良く、まるで抵抗を感じません。スルスル書けます。ペン先が見えるためズームしなくても細かい入り組んだ線が簡単に書けます。デザイナーさんが絵を描くときに使えそうです。


■まとめ

これまで様々な種類のスタイラスを試しましたが、全く新しく他に例がない独特なスタイラスだと思います。書き味も独特です。個人的には手書きで文字を書いたりするときには Pogo Sketch のほうが実際のペンに近い感覚なので好きなのですが、細かい線を引いたりデザインをしたりする人にはこちらの方が大きくて安定し接地面を見ながら描くことができるのでお勧めです。

2010/10/10現在はまだ価格も高く簡単には手に入らないのですが、今後量産が進むかどうか楽しみなアイテムです。


■2010/10/30追記

作者のAndrewさんから次の量産型モデルについてお知らせをいただきました!それによると、次のモデルはなんと半額の$37.50、今の円高を利用すれば3000円とちょっとで買えるようになるそうです!送料は以前かかりますがかなりの進歩だと思います。

また、以前使っていたときに表面にうっすら傷が入ったと書きましたが、新しくシートを貼り替えて一ヶ月ほど使っていますが全く傷が入っていません。使い方の問題か、もしくはシートの品質の問題だったようです。また、次バージョンではさらに傷がつきにくくなるよう先端の加工が見直されるとのことなので、ますます楽しみです。


■2011/02/01追記

http://ostylus.com/order.html から$37.50と送料で購入できるようになっています!私が持っている早期生産モデルから改良も施されているようです。

2010年10月5日火曜日

Android のメモリ管理は大変です

■理想

AndroidってJavaだからメモリ管理なんてしなくてもいいよね!!


なんて思っていた時代が私にもありました・・・


■現実
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 画面が回転した時など、Activityが破棄されるときに呼び出されます
        // すべてのメモリはここで開放します
        // - 特に危険なのが内部クラス(MyWebChromeClientなど)、正しく開放しないとActivityが開放されません
        // - セットしたbackgroundのcallbackもnullにしないと開放が行われません
        // - webViewのdestroy()を忘れると後からGCが走ったときにVMがクラッシュします
        this.webView.stopLoading();
        this.webView.setWebChromeClient(null);
        this.webView.setWebViewClient(null);
        this.unregisterForContextMenu(this.webView);
        this.webView.destroy();
        this.webView = null;

        Drawable backgroundDrawable = this.backgroundViewGroup.getBackground();
        backgroundDrawable.setCallback(null);
        this.backgroundViewGroup.setBackgroundDrawable(null);
        this.backgroundViewGroup = null;
        this.mainViewGroup = null;
        this.anotherViewGroup = null;
    }
これだけやらないと平気でクラッシュします。マジです。しかもGCが走るまでクラッシュしないとかそういう厄介すぎる現象に2回ほど遭遇しました。

WeakReferenceとか使えばいいんでしょうけど面倒なんですよねぇ。


■Google曰く

http://d.hatena.ne.jp/nakamura001/20101002/1286015483

iPhoneより普通に厳しい気がするのは気のせいでしょうか


■結論

AndroidはJavaではないのできちんとメモリ管理をしましょう!

2010年9月12日日曜日

iPhone 開発規約まとめ

あんまり iOS 上での開発規約とか見かけないので、試しに私が今個人/会社で使っている開発規約を公開してみることにしました。


■設計

設計は所謂 MVC と呼ばれる設計モデルを採用します。ただし、厳密な MVC というわけではなく、以下のような区分になっています。
  • Model
    Core Data を使用します。通常 MVC での Model というと業務ロジック等を含めた業務モデル一般すべてを含むのですが、私の場合は特に Core Data の NSManagedObject を Model として扱い、 Model 単体のみで完結するロジックのみを Model に記述します。たとえば、
    • Core Data から対象の Model とその関連 Model 取得
    • Model の新規作成
    • 新規作成時、更新時に自動的に Model のプロパティを更新する
    • Model のプロパティの値を元に幾何学計算をしたり、一時的に使うキャッシュを用意したりする
    などです。実際のコードのサンプル (ニュースサイトのニュースを表す News モデルの実装) はこんな感じになります:
    #import "News.h"

    @implementation News

    @dynamic body;
    @dynamic title;
    @dynamic imageURL;
    @dynamic objectId;
    @dynamic dateCreated;
    @dynamic dateUpdated;
    @dynamic sortOrder;

    @end

    //----------------------------------------------------------------------------------------
    // ここから上は .xcdatamodel ファイルから自動生成されたコードなので触れないようにします。
    //----------------------------------------------------------------------------------------

    #import "AppDelegate.h"

    @implementation News (NSManagedObject)
    - (void)awakeFromInsert {
    [super awakeFromInsert];
    // Insert
    self.dateCreated = [NSDate date];
    }
    - (void)willSave {
    [super willSave];
    // Update
    // DO NOT DO THIS in here because updating self.dateUpdated will call -willSave method recursively until this application crashes!
    // Use NSManagedObjectContextWillSaveNotification / NSManagedObjectContextObjectsDidChangeNotification and watch the time delta of previous change
    // If the time delta is small enough to ignore, do not update dateUpdated property to avoid infinite loop
    //self.dateUpdated = [NSDate date];
    }
    - (void)awakeFromFetch {
    [super awakeFromFetch];
    // Select
    }
    @end

    @implementation News (DBAccessors)
    + (NSArray *)all {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"allNews"];

    // Sort by sortOrder, ASC
    NSArray *sortDescriptors = [NSArray arrayWithObjects:
    [[[NSSortDescriptor alloc] initWithKey:@"sortOrder" ascending:YES] autorelease],
    nil];
    [request setSortDescriptors:sortDescriptors];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return nil;
    }
    return resultArray;
    }
    + (id)get:(NSString *)targetId {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSDictionary *substitutionVariables = [NSDictionary dictionaryWithObject:targetId
    forKey:@"objectId"];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestFromTemplateWithName:@"getNews"
    substitutionVariables:substitutionVariables];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return nil;
    }
    return [resultArray lastObject];
    }
    + (void)deleteAll {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"allNews"];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return;
    }

    // Delete all objects fetched
    for (NSManagedObject *obj in resultArray) {
    [appDelegate.managedObjectContext deleteObject:obj];
    }
    }
    @end
  • Operation
    本来の MVC モデルには存在しませんが、私は特別に Operation という区分をもうけています。定義は以下の通り。
    • 一つの業務モデル、業務ロジックを完結させるひとまとまりのロジックであること。要するに本来の MVC モデルの業務ロジック的要素であること。 もっと言うなら Operation だけを切り出して単体テストできること。
    • それなりに重要でかつ量があり、 Controller や Model から共有的に呼びだされること。
    • 非同期で処理ができること。
    • NSOperation クラスのサブクラスにすること。
    たとえばインターネット経由で API を実行して結果を Model に突っ込む処理などは Operation として実装しています。 NSOperation として実装することで、非同期処理が簡単にできるようになるだけではなく、 タスクの依存関係を決めて先に親タスクが実行されるのを待つようにしたり、現在の状態を厳密に判定したり、タスクをキャンセルしたりするのが容易になります。また、将来的に iPhone / iPad のCPUがマルチコアになった場合、 NSOperation を継承しておけばほとんど何もしなくてもその恩恵を受けることができるはずです。
  • View
    ビューです。主に UIKit および Three20 を用いて構築し、必要に応じてその他のビューライブラリを組み込みます。個人的には UIView のサブクラス一般をすべて View として扱っています。 View には描画ロジックと幾何学計算などの補助ロジック、およびそれに必要な最小限のデータのみを持たせるようにします。状態を持ってもかまいませんが、状態のコントロールを View 自身が行うのはできる限り避けます。タッチイベントのハンドリングは必要に応じて View で touchesBegin: などを実装して行いますが、基本的には Controller 側で UIGestureRecognizer を用いて行います。
  • Controller
    コントローラーです。私の中での定義は UIViewController のサブクラスです。アプリの内容に応じて UIKit と Three20 を使い分けます。 本来 Controller は全体の流れのみを管理し実際の処理を行ってはならないということになっていますが、 UIViewController の性質を鑑みて、私は次のように Controller の役割を定義しています。
    • View の管理。
    • 画面遷移, 要するに Controller 間の遷移の管理。
    • タッチイベント、ジェスチャイベント、加速度計などからの入力の受付と処理。
    • 各種 UI コンポーネントの Delegate 処理。 UITextView, UIPopoverController, UIActionSheet, などなどなど多岐にわたります。
    • Operation 完了時の処理など、各種 Notification を受け付けて処理。
    • 上記に必要になるデータ/メソッドの定義。 View に持たせるぐらいならこちらに持たせる。他に持たせるところがないなら Controller にすべて持たせる。
    ということで、 Controller を名乗っていますが実際にはかなり自分で処理をします。こんなのいちいち分けていたら大変ですし・・・といういいわけです。実際のコードサンプルはお見せできないのですが、実装の概要はこんな感じです



  • Application Delegate
    これも本来の MVC モデルには存在せず、本来は Controller として扱うべき物だと思いますが、私は特別に分けています。 Application Delegate は所謂アプリケーショングローバルなデータや設定、オブジェクト、ユーティリティメソッドを管理するものとして扱います。たとえば、
    • Operation 駆動用の NSOperationQueue
    • Model のための NSManagedObjectContext / NSManagedObjectModel / NSPersistentCoordinator
    • 現在通信可能か否かを判定するためのユーティリティメソッド
    • アプリケーショングローバルな Notification を受け取って Application Delegate のプロパティとしてセットする
    などです。これらはほぼすべてのアプリで絶対に必要になるのでテンプレ化しています。


■Xcodeプロジェクト

プロジェクトのグループはこんな感じで分けます。



ビルド設定については、プロジェクト全体のビルド設定は可能な限り使わないようにして、ターゲットごとのビルド設定 ( Cmd+Option+E ) を主に使います。ターゲットが例え複数に分かれても常に同じ物を使うところはプロジェクト全体の設定を変更します。


■命名規則

大体以下のような方針でやっています。
  • Foundation や UIKit など Apple の使っている命名規則に似せること。似てないシグネチャは即リファクタリング対象。
  • ミススペル、Typo、意味のわからない英単語 ( registとか ) は一文字一単語であろうとも発見した瞬間即リファクタリング対象。英語辞書のソースは http://www.alc.co.jp/ とする。
  • 名前はどれだけ長くなろうとも絶対に省略しないこと。 updUniqUsr とか発見したら即リファクタリング対象。逆に BPSuperDuperViewControllerDidFinishedUberTaskNotification とかは表彰モノ。
    • これを BPSDVCDFUTN とか略した奴が現れたら即 SATSUGAI モノです


■まとめ

ここまで書いておいて何ですが、要するに 一貫した基準があるなら 自分らの好きなようにするのが一番いい のかなと思います。

idea mapper for iPad が有料ランキング一位になりました



弊社ビープラウドが開発を担当しました idea mapper for iPad がなんと日本の App Store で有料ランキング一位を取ってしまいましたので、記念POST。 Good Reader や i文庫HD より上ってマジですか・・・>< ほんと企画とデザインをされたサイテックさんのおかげだと思います。ありがとうございます。

ところで気になったのがランキングの推移の仕方。このアプリ、実は日本の有名どころのレビューサイトに全然レビューして貰ってないんです。少なくとも私が自分で観測した範囲では、2010/09/12現在 AppBankさんにもapptoiさんにもラボさんにもお宝鑑定団さんにも掲載されていなくて。実際初日は仕事効率化カテゴリ30位ぐらいだったのに、一週間ぐらいかけてじわじわ伸ばしてきて今の順位に。

これは個人的にはかなり衝撃でした。というのもことあるごとに聞いていたiPhoneアプリのセールス/マーケティングの基礎が、「大手のレビューサイトにレビューを載せて貰う」ことだったからです。ではどこからこれだけ人が流入したのか?と考えてみたところ、サイテックの社長さんが凄く積極的にTwitterで宣伝してくださって、口コミでどんどん広がっていき、ランキングに載ってそのまま加速度的にダウンロードが増えたのではないかなと思ってます。Twitterの力は偉大と考えるべきなのか、日本の App Store の規模が小さいおかげで助かったと考えるべきなのか、どっちなのかは分かりませんが・・・何かの参考になれば幸いです。

2010年9月5日日曜日

Three20 の TTURLRequest は POST メソッドのリクエストもキャッシュしてしまう

Three20 には TTURLRequest という Three20 フレームワークの通信関連を一手に引き受けている通信用クラスがあります。基本的には NSURLConnection クラスとほぼ同等なのですが、リクエストを作ったりレスポンスを delegate でハンドルするのがより簡単になるように作られていたり、独自のキャッシュを使用してより効率的なキャッシュをするようになっていたり、様々な点で NSURLConnection より優れていて便利に使えます。

ですがいくつかハマリどころもありまして、今回はそれを紹介します。ちなみに今回使用した Three20 のバージョンは http://github.com/facebook/three20 の default ブランチの 2010/09/05 付での最新コミットです。今後修正される可能性があります。

実は TTURLRequest は、デフォルトでは HTTP メソッドの種類に関係なく、一律すべてのURLをキャッシュするようになってしまっています。そのため、キャッシュ設定をせずに POST メソッドを使って Web API を実行したり、 RESTful な Webアプリ に PUT や DELETE を送ってしまうと、二回目以降のリクエストがキャッシュされてしまいサーバーにリクエストが飛ばなくなってしまいます。回避方法は以下のどちらかを使うと良いです。
  1. 手動でリクエストを作成する際にキャッシュ設定を明示的に指定する
  2. フレームワーク側を書き換えてしまい、 POST, PUT, DELETE 実行時にはキャッシュを無視するようにする

■手動でリクエストを作成する際にキャッシュ設定を明示的に指定する
一番簡単です。以下のようにして明示的にキャッシュを使わないように指定します。
TTURLRequest *request = [TTURLRequest requestWithURL:@"http://mypage.example.com/api/something/post" delegate:self];
request.httpMethod = @"POST";
request.cachePolicy = TTURLRequestCachePolicyNone;

■フレームワーク側を書き換える
でも個人的には GET 以外でリクエスト結果がキャッシュされるのは誰がなんと言おうと不具合だと思っているので、 TTURLRequestQueue.mloadRequestFromCache: メソッドを以下のように書き換えて対応しました。
- (BOOL)loadRequestFromCache:(TTURLRequest*)request {
if (!request.cacheKey) {
request.cacheKey = [[TTURLCache sharedCache] keyForURL:request.urlPath];
}

if (IS_MASK_SET(request.cachePolicy, TTURLRequestCachePolicyEtag)) {
// Etags always make the request. The request headers will then include the etag.
// - If there is new data, server returns 200 with data.
// - Otherwise, returns a 304, with empty request body.
return NO;
//-----------------------------------------------------------------------
// ここから下が変更点
//-----------------------------------------------------------------------
} else if ([request.httpMethod isEqualToString:@"POST"] || [request.httpMethod isEqualToString:@"PUT"] || [request.httpMethod isEqualToString:@"DELETE"]) {
// HTTP POST/PUT/DELETE should not use cache.
// Only HTTP GET can use this cache.
return NO;
//-----------------------------------------------------------------------
// ここまでが変更点
//-----------------------------------------------------------------------
} else if (request.cachePolicy & (TTURLRequestCachePolicyDisk|TTURLRequestCachePolicyMemory)) {
これですべてのHTTPリクエストにおいて、 POST, PUT, DELETE 時にキャッシュが使われなくなります。

2010年8月29日日曜日

自分なりの iPhone アプリ開発手法とかこだわりとか書いてみた

Twitter で vの人こと @voluntas さんに されたので、自分なりのポリシーとかこだわりとか開発手法とかをまとめてみることにしました。今仕事で iPhone アプリの開発を主にやっているので、 iPhone アプリに関する内容が多いですが、それ以外の開発でも使えると思います。

あまり技術的な内容やツールに関する内容はありません。それらは別エントリーにまとめようと思います。


■大前提: 自分を知る
まず何はなくともこっからです。なんだか開発とか全然関係ないじゃないか、怪しい自己啓発じゃねえかと思われるかもしれませんが、敵を知り己をを知れば百戦危うからずと昔のエライ人も言ってます。それにそもそも私がどのような人間なのかを理解しないと、せっかくの開発手法もそのまま真似してはうまく合わない・上手く回らない・賛成できないということになりますので、非常に大事だと思います。ということでさっそくまとめてみました。
  • 恐ろしく几帳面である
    A型人間です。
    Java とか大好きです。逆にカオスなコードが苦手で、 PHP とかは敵です。
    掃除してない @feiz@shin_no_suke の机とか見てると勝手にゴミを捨てたり掃除したくなります。
    Typo とか見つけたら人のコードでも勝手に直します。 Typo するのが嫌なので毎回変数名を決める前に英語辞書を引きます。
  • ゆとりである
    一日の労働時間は八時間までよねー(キリッ
    なのでとにかく楽をしたがりです。
    そもそも体力が小学生並なので徹夜仕事とかできません。そのような事態に陥らないような仕事の進め方が必要になります。
  • やたらこだわる
    好きなモノは徹底的に好きで嫌いなモノは徹底的に嫌いです。なので評価が極端で言動が偉そうです。すみません><
    一歩間違えるとただの頑固者になるので気をつけています。
  • 言動は偉そうだけれども中身は非常に臆病者で弱い
    なのでゆとりを失うと一気に迷走します。余裕を常に持てるようにする必要があります。
    また周りの皆さんの支援ないと生きていけないと思っていますので、「生意気だけれどもイイ奴」ぐらいのポジションに落ち着きたいと思ってます。
  • iPhone は俺の嫁、 Cocoa Touch は神環境
    もう二度と iPhone の無い世界には戻れません。
    10年後ぐらいにはまた別の面白いものがあると思いますが、今のところは iPhone が一番面白いです。
似たような方は私と同じやり方を真似してもいいし、真逆な方は私のやり方と真反対をやればうまくいくのではないかと思います。


■三つの大戦略
私がどういう人間か分かったところで、次は今の仕事のやり方に関して最も根底にある三つの大戦略をまとめてみました。この大戦略にはほとんどツールや言語の話が登場しません。それらは時代が変われば変化してしまうからです。それよりもむしろ、どういう考え方やポリシーで仕事をすれば最も開発生産性が上がるか、というところに着目しています。
  • 徹底的に几帳面にやる
  • 楽をする・楽しむ・楽をさせる・楽しませる
  • 二度とガラケーには戻らないと決心しその通りに行動する
徹底的に几帳面にやる
几帳面なのが武器なので、それを徹底的に生かす方針で開発を進めます。また几帳面さは Objective-C と非常に相性がよいです。たとえばメモリリークが発生してから調査すると非常に大変ですが、発生する前に防ぐことができれば生産性がそれだけ高まるはずです。また KVC/KVO や Core Data など動的言語のような処理を行う箇所では一文字のスペルミスが発見しづらいバグにつながるため、スペルミスチェックを行ったり、命名規則の策定をしたりする必要がありますが、このとき几帳面さが効果的に働きます。
また、余りつまらないところでミスを連発するとテンションも下がるしリズムも崩れ、余裕が無くなってしまい結果として悪いモノができあがってしまうので、それを防ぐためにも几帳面にやるのが効果的だと思ってます。
なにより、これが一番大事だと思うのですが、几帳面にコードを書くのが自分にとって一番楽しく、モチベーションが上がります。つまり逆に言えば、リズミカルにどんどん細かいところを気にしないでガーッとたくさん作るほうが楽しくリズムに乗れてモチベーションも上がる人でしたら、そのようにする方が良いと思います。

楽をする・楽しむ・楽をさせる・楽しませる
四楽運動です(何それ
要するに、
自分が楽したい、仕事量は減らして簡単にしたい
仕事は楽しんでやりたい、面白い技術を使ってやりたい
でも自分ばっかり楽したら申し訳ないし楽した分だけしっかりやって楽をさせてあげよう
それにせっかく作るなら楽しいモノを作った方がいいに決まってるからそうしよ
ということです。
この方針に従うと、私の場合、楽ができて楽しいだけではなく、「このようなすばらしい環境で開発させて貰ってありがとう」という感謝の気持ちと「それなりの成果を出さなくてはならない」という責任感が自然と発生するため、非常に開発効率が高まるようです。

二度とガラケーには戻らないと決心しその通りに行動する
うちの会社でもガラケー向けのWebアプリの開発をやっていて、その際の苦労話を聞く機会があります。また、企画やお仕事の提案などでGREEやモバゲーなどのガラケーアプリを見せて貰うことがあります。そのような経験から思うのは、
ガラケーはIE6とか比較にならないほど苦労するくせにどうしようもないユーザー体験しか提案できない最悪の開発環境
だということです。たとえばメールの文字コードの扱いとか、特定の機種だけ動かないとかで50台以上の実機でテストしないと駄目とか、Flash lite 1.1はまともに使えないからサーバー側でバイナリ操作してSWFを生成して送り返しているとか、そんなネタを聞くたびにこう、ふつふつとしたものが沸いてきます。
その上お金になるからかバッドノウハウだからか知りませんが、驚くほど情報が出回っていない。調べてもなかなか分からない。とにかく開発者に優しくありません。なによりバッドノウハウを運用しても楽しくない上にイライラして嫌なムードになってしまいます。
確かにガラケーの環境はお客さんからお金を取りやすく儲かりやすいため、会社で取り組むには非常に好都合であるとは思うのですが、やはり私は開発者の身である以上、素直に開発者に優しく、さらには素晴らしいユーザー体験を提供できる環境を選びたいのです。さらに思うだけでは駄目で、実際に iPhone の開発で仕事を取って継続的にお金を稼げるようにしなくてはならないので、そうなれるよう実際に行動する必要があります。


■「徹底的に几帳面にやる」を達成するための戦略
  • 面倒くさがらずにリファクタリングする
    リファクタリングは大事です。納得いくまでリファクタリングします。一文字Typoしたら名前変更、名前が後から気に入らなくなったら変更、クラスの構造を変更、メソッドをパブリックに変更、エトセトラエトセトラ。リファクタリングは他の人がコードを読んだ時の理解を助けてくれるだけではなく、自分自身がリファクタリングのためにコードを理解することになり、今後の設計に役立ちます。自分の考えを整理するという意味でも大事だと思います。
  • 細かくスタートして、ちょっと作って確認し、またちょっと作って確認して繰り返す
    特に iPhone での開発は小規模で変更や追加が多く、アップデートも頻繁。必然的にアジャイルな開発が要求されます。そしてなにより、テストケースを書いてテストドリブンで開発するだけの時間的余裕がありません。そんな贅沢なやり方ができるのは長期でずーっとやっていくWebサービスや大規模なシステムだけで、作ってすぐ放棄される事が多い iPhone の開発にはテストドリブンは全く向いていないと思っています。
    しかしながら、品質は当然担保しなければなりません。そこで私は小さく作ってすぐに作った箇所を確認し、また少し作って確認・・・という手法をとっています。正しく作る単位をモジュール化することができれば、一端テストしてその部分の品質が保証されればその後の開発ではテスト済みのモジュール内の事は考えなくて済むからです。テスト自体が大変でコストがかかるので、やはりコーディングの段階で几帳面にバグを出さないようにするというのが一番に思えます。
  • 一番面倒で難しそうなところからスタートする
    ほとんどの技術者の方はこのような手法を取られていると思うのでわざわざ書くほどのものでもないのですが、それでも一番面倒で難しそうなところからスタートする手法は非常に有効です。上記の手法とも合わせ、一番難しそうなところ一番最初に小さくスタートしてすぐにテストし、品質を保証してしまうのが最も有効です。全体の2割のコードに全体の5割ぐらいの時間をかけて作り、残りの半分はコピペしてプロパティをちょっと書き換えるだけでざざーっと作れるようなところにしてしまう、というのが理想だと思ってます。
■「楽をする・楽しむ・楽をさせる・楽しませる」を達成するための戦略
  • 取捨選択をする
    ソフトウェア開発で楽をするための一番よい方法は、作らないことです。取捨選択を適切に行ない、不要な枝を切り捨て、必要な枝・競争力のある枝にすべてのエネルギーを集約することで、個性的なアプリを作り出すことが出来ます。個性的でないアプリは供給過多の市場に埋没します。さらに取捨選択は作業をすすめる最中でも大いに役立ちます。たまにお客さんから「あれもなる早、これもなる早、全部最優先で」とかいうお前日本語間違ってるだろといわんばかりの指示が飛んできたりしますが、これは全く話になりません。「最たるもの」とは常にひとつしか存在しないもので、だからこそまずそれから仕事にとりかかることができ、余計な仕事の順序選択思考を避けることができるのです。私自身が優先順位をつけたり指示を出したりする際にも、最優先がひとつ、次に優先がふたつ、それ以下はすべて後回しか切り落としです。そもそも、三つよりたくさんを記憶して同時に処理できる人はまれです。
  • 偉い人の作ったフレームワークとかライブラリをどんどん使わせていただく
    ソフトウェア開発で楽をするための二番目に良い方法は、すでに完成しているものを取り込むことです。ということで、フレームワークやライブラリなどは積極的に活用します。ライセンスには気をつけないと大変なことになりますので注意ですが、これらのフレームワークやライブラリは私よりも遥かにすごい人達が時間と手間をかけて作り上げられ、テストもしっかり行った上で公開されているものが多いので、私自身が実装するよりも遥かに信頼できると思っています。もちろん人が作ったものなのでバグや合わない仕様はたくさんありますが、ソースコードを読んで修正すればよいのです。ゼロから自分が作るよりは断然良いです。
  • とにかく自動化する
    とかく人間というのは、単純作業をミスすることにかけては超一流です。頻度の差こそあれ誰しも必ず単純作業をミスします。さらに単純作業は集中力という貴重なリソースを消費します。
    二回以上繰り返す単調作業は自動化する。それがたとえ、ソースコードをzipで圧縮してメールでお客さんに送るといった簡単な作業であったとしても、自動化しておくのは非常に役に立ちます。単純作業を避けることで生まれた余剰集中力を、別のところに振り分けることができます。
    自動化する手法がわからなかったら調べて勉強します。ここでちょっとわからなくて面倒だからと避けると後から地獄を見ますし、成長できないので、意地でも調べます。
  • 自分に要求されている責任範囲以上の仕事を引き受けて楽をさせてあげる
    私は最初の会社の新人教育の際に、「自分が要求されている以上の仕事をしろ」と繰り返し教えられました。実際最初の会社はどちらかというとブラックに属する部類だと思いますし、こういう教えは社畜だの何だのと言われているようですが、私はこの「自分が要求されている以上の仕事をしろ」という考えが好きです。
    確かに前の会社で仕事をしている間はこの考えは嫌いでした。なぜなら仕事がつまらない上に余計なことをすると怒られる職場だったからです。バグだらけの共通ライブラリを修正したら余計なことをするなと怒られ、酷い出来栄えのJavaScriptを救済するためにjQueryを突っ込んでみたらこれまた余計なことをするなと言われ。まったくどうしろと・・・。
    しかし今は違います。仕事の内容は全部任せていただいていますし、効率化も推奨されています。画像リソースがお客さんからやってこなければ自分で作り、画面の仕様が決まらなければ私が決めて作ってお渡しし、セールスのためにアプリレビューを行ってくれるブログ一覧をまとめてお渡しするなど、とにかく仕事が円滑に進むと思えばなんでもやります。面倒なときもありますが、それより相手の仕事が遅くってイライラして「あいつが悪い、あいつのせいでプロジェクトが失敗した」とか考える方が嫌です。それにちょっと勝手にやりすぎたかと思っても、意外なほど喜んでもらえます。
    ただし、何でもかんでもこの方法で一人で引き受けていると私の仕事量が破綻するので、この方法で引き受けるのは一過性の仕事だけにしています。定期的にやらなくてはいけない仕事は自動化するか、やり方を教えてあげてやってもらいます。
  • 細部にこだわれるだけのスケジュール上の余裕を常に持つ
    リファクタリングしたり、自分に要求されている範囲以上の仕事をすると、どうしても当初見積りより遥かにたくさんの時間がかかることになります。それにプロジェクトには絶対にトラブルが付きものです。ということで、時間的なゆとりが必要です。私は見積りを提出する際に、自分で余裕を持ってできる見積りに、さらに1.5をかけて提出したりします。提出するときにはこれ時間がかかりすぎじゃないかと言われないかヒヤヒヤしますが、バグを取ったりクオリティを上げたり、突然湧いたトラブルに対処しているうちに、最終的にはそれで大体丁度良くなるので不思議です。お客さんにとっても納期遅延が発生しないため、今のところ大変ご満足頂いております。
    特に iPhone アプリはこの余裕を持ってアプリのクオリティを上げる方針が有効に働きやすい気がします。修正が比較的簡単ですぐに行えるWebアプリに比べ、リリースを急いだとしても、ひとたびバグが原因でリジェクトされるとリリースが一週間遅れ、醜いアプリをリリースするとレビューで酷評されと、ろくなことがありません。
■「二度とガラケーには戻らないと決心しその通りに行動する」を達成するための戦略
  • ガラケーの仕事は意地でも断る
    「ガラケーは嫌いだけれど、仕事だからしょうがないし・・・」冗談じゃありません。幸いにして iPhone 開発で人材募集をしている会社さんも増えましたし、フリーで出来るお仕事の量も増えています。口だけでなくて行動でやらなければなりませんので、まずガラケーの仕事をお断りです。
  • せっかく無理言って iPhone のお仕事をやらせてもらっているので、とにかく全力でやる
    とまぁこんな具合で無理わがままを通しまして何とか iPhone のお仕事を頂きましたので、その分きっちりお仕事して成果を出して次があるようにしなければなりません。こうして後ろを断つとやっぱりやる気が出ます。また、損益にも気が向くようになります。なにせ赤字垂れ流しではあっという間にガラケーに逆戻りです。利益を出さなくては話になりません。
  • 自分に要求されている責任範囲以上の仕事をやる
    一番信用できて一番自分の思い通りに動くのは自分です。まず自分がやることで成功が近づくと考えています。と同時に一番信用できないのも自分なので、人におまかせしたり既存のライブラリなどを活用したりというのも必要になります。なんかすごい矛盾してますが、とにかく自分が一番信用できるけど一番信用できないのです。
  • 次の仕事を持ってきてもらえるように売上とかまで気にする
    「開発は物つくるまでが仕事、売るのは営業の仕事・・・」ガラケーでやってください。 iPhone 開発者の人は少人数かフリーの人が多いため、みなさん作ったものを売るところまで考えてらっしゃるようです。ただし、あくまで開発者の立場として売上を気にすることです。本職の営業や企画の方がいらっしゃるときに、私が営業や企画の真似事を始めると破綻します。
  • 以上のようなわがままを聞いてくれる環境に身を置く
    「んなもんお前に言われなくてもわかってるわ!そんな理想的な環境あるわけねえ!」すみません、ごもっともです><
    結局良い環境で仕事するのが一番で、良い環境は良い人脈と人付き合いがあれば勝手にやってくるみたいなので、まずは勉強会とかに参加しまして良い人付き合いをするのが一番の近道かなぁと思います。
  • 成果はきっちりアピールする
    成果はアピールしないと次につながりませんので、きちんとアピールします。幸いにして私は口から先に生まれてきた人間なので、大声で騒ぐのは得意です!しかしアピールというのはやり方を間違えると単なるスパムや嫌がらせに成り下がってしまいます。TPOをわきまえて正しく成果を大声でアピールしましょう。
    たとえば @iphone_dev_jp に「新作アプリをリリースしました!」なんて大声で流してもイラッとされるばかりですが、リリースして培った経験や技術情報、お役立ち情報を流せば「あいつはできる」と思ってもらえるわけです。
■チームで作業する際の戦略
ここまでは主に自分自身に関する内容の戦略でした。が、実際の仕事はもちろん自分ひとりだけではなく複数の関係者やチームのみなさんと進めることになります。ということで最後にチームで作業する際に私の考える戦略をまとめてみました。
  • 大前提: 人数は少なければ少ないほど良い 能力は高ければ高いほど良い 距離は近ければ近いほど良い
    これが大前提です。とにかくコストを最小限に保ちつつ、時間あたりの開発能力を最大にする必要があります。大規模開発等では当てはまらないかもしれませんが、仕事の総量が比較的少なめで予算が少なく納期が極めて短い iPhone 開発では、開発・デザイナーあわせて殆どの場合3人以内で足ります。その分各個人の責任範囲が非常に広くなるため、すべてのチームメンバーにそれらの責任を全うできるだけのスキルが必要です。誰しも限界があるので最終的には人に聞いたり頼ったりすることになると思うのですが、どこまで個々人が自分自身で調べて解決できるかというところが大事だと思います。何かあったらすぐ頼るような状態では生産性が落ちます。
    「距離は近ければ近いほど良い」というのは物理的にも精神的にもです。物理的に近いほうがやりやすいというのは、極端な例で言えばお客さんが隣のフロアにいるとかです。何か困ったことが発生したときに、メールのやり取りや電話口でのやりとりで解決するより、走って行ってその場で手取り足取り説明したほうが断然早いしうまくいきます。精神的に近いほうがやりやすいというのは、仲がいい方がチームのムードも良くなるし対立が発生しづらいからです。個人的には、良いチームにはデルタフォースとかSASのような特殊部隊のイメージがあります。あのへんの特殊部隊の人が語るチームマネジメント本とか講演会とかあればいいなぁとか思います。
  • 少数・精鋭・見知った仲でチームを結成する
    • 少数になるようにする - 必要最小限の人以外を入れない
    • 精鋭になるようにする - 可能であればそうするのがよいが、できる範囲でやるのであれば、同じ程度の実力の人を集めてチームにする
    • 見知った仲になるようにする - まず知り合う、勉強会とかに行く、一緒に遊ぶ、共通の趣味嗜好を持つ、距離の近いお客さんを選ぶ
    人を増やすのは比較的簡単ですが、減らすのは恐ろしく難しいです。人が増えると責任が薄まります。「俺がいなくてもどうにかなる」と気づいてしまうとヤル気が極端に下がりますし楽しくありません。もちろん、人が足りなくて毎日徹夜しないと追いつかないような状態になっているのはダメで、バランスが重要ですが、基本ほんの少しだけ足りない側に倒すほうが良いかと思ってます。
    チーム全体が精鋭になるようにするというのは、できるのであればそれが最も素晴らしいことだと思いますが、まぁ現実的に考えて難しいと思います。そんな手段があれば私がまず知りたいです>< で、現実的な作戦としては、チーム全体の実力をできる限り均一化すると言うことが挙げられます。チームメンバーの実力にほとんど差がない状態が、一番チームメンバー個々の能力を最大限に発揮させ、責任感を最大限に発揮させることができると思うからです。一人だけ精鋭がいてもその人に負荷と責任が集中し、他の人はアイツがいるからと思ってしまいます。逆に一人だけおちこぼれるとみんながその一人のせいにして責任を放棄します。
    見知った仲になるようにするために、例えば社内で月一ピザでも食べながら勉強会をやるとか、 誰か の家に飲みに行くとか、お昼ごはんを一緒に食べるとか。ここが一番難しい気がしますが、一番大事なところだと思います。仲がよければちょっとやそっとのミスや負荷があっても許してあげられるようになります。逆に険悪になるとほんの少しのミスや負荷が許せなくなり、さらに事態が悪化します。
    ただし、仲が良いのと馴れ合うのは絶対に違うと思います。馴れ合い始めると新しい人が入ってこれなくなり、仕事に張りがなくなります。これもバランスですね。
  • 開発チームとステークスホルダーが直接コミュニケーションできるようにする
    いくつかプロジェクトをやって気づいたことがこれで、開発チームとステークスホルダー(最終決定権保持者、各種画像リソースなどのリソース提供者)の距離が遠いとプロジェクトが失敗しやすくなります。距離が遠いというのは、間に仲介役 - たとえば営業の人だとか - が入るということです。人ほど信用ならない情報伝送経路はありません。必ず情報の劣化・嘘の混入・膨大な遅延が発生します。このような伝送経路を使うのをやめて、直接ステークスホルダーの人とSkypeでやり取りできるようになると、効率が格段によくなります。ミスも発生しません。
  • それでもどうしても何ともならないとき
    • 人数が増えてしまった - チーム/責任範囲を分割して可能な限り少数にする、または別の仕事を取ってきてそちらにチームを分割する
    • スキルが低い人と一緒に仕事をする必要がある - 低い人の理解に全員が合わせる。
    • 顔も名前も知らない人がチームに入ってきた、どうしてもウマが合わない人がいる - そこでどう仲良くなるかというのが本当にカッコイイ大人のやることではないか(キリッ
    • 物理的に距離が遠い - ツールを最大限に活用する。ツールを導入できるように先方をうまく説得する。
    • ステークスホルダーと直接話しにくい - 無理矢理にでも直接話すのを試みる
    すみません、かなり無茶苦茶です>< というのも私自身ここに書かれているような事態にうまく対処できないからです。実際これらの問題をうまく乗り切られる人は、人間力があるといいますか、磨かれた大人であるといいますか、優れたマネージャーさんであると思います。現実問題、こういった問題は数限りなく存在しますので、具体的な対処策は私自身学び取りたいところです。ですが、絶対に勘違いしてはならないのが、これらはあくまで「対処療法」であって、「根本的な治療」ではないということです。最初から対処療法が必要でないような環境を作るために尽力し、そのような環境で仕事をしていれば、これらの問題はそもそも発生せず、そのため余力を生むことができます。発生した余力はさらに理想的な環境を整備するために使ったり、自分自身のスキル向上のために使えます。
    これを体の健康にたとえるならば、毎日暴飲暴食せず野菜を取り適度な運動を行ない十分な睡眠をとることで病気にならない様にするのが一番であって、自堕落な生活を送った結果重い病気にかかったので医者に行って治してもらうというのを繰り返すようでは体がどんどんぼろぼろになっていきます。体の健康に例えると、前者を行う人が尊敬され後者は笑いものにされるのですが、チーム運営となると前者は魔法か理想論と馬鹿にされ後者の出来る人が大人だと尊敬されているようで、どうも私はしっくりきません。何事も予防する、予防できる環境を作るのが一番です。発生してから耐えるのは美徳ではなくやむを得ないことです。
  • 課題: どうしても合わない人をどう追放するか
    これは世界全国、人とチームがある限りの永遠の課題だと思います。本当はこんなことをしないでよいのであれば理想なのですが、それでもどうしても人が余ったりチームに合わないからという理由で人を外さなければならないことがあります。アメリカ人みたいに非情にズバズバ首を斬ると、後から衝突が発生したりチームの財産を平気で奪って逃げたりされます。かといって日本人みたいに何時までも何時までも衝突を恐れて首を切れないと、チーム全体の死活に関わります。まったく、どうするのがいいんでしょう・・・><
■まとめ

3行でまとめると、楽しく生産性の高い仕事をするには、
責任感を持つ/持たせるのが大事
ゆとり大事
バランス大事
こんなところですかね。

2010年8月15日日曜日

CALayer を使って UIImage を描画する

UIImage を高速で描画する必要がある案件に遭遇したため、 CALayer を使ってみました。 CALayer と聞くとなにやら難しい感じがしますが、実際に使ってみると非常に簡単で高速です。

CALayer を使うと良い場面は以下のような場合です。
  • 画像を大量に描画する必要がある
  • 画像を高速に描画する必要がある
  • 画像を高速に変形・移動する必要がある
  • CGContextDrawImage を今使っている箇所がある
とくに変形に対して非常に強いです。 CGContextDrawImage で変形後の UIImage を再度描画し直したりするのに比べると、 CALayer の変形は格段に高速に動作します。


■実際に描画してみる

まず最初に <QuartsCore/QuartsCore.h> をインポートします。 QuartzCore.framework をプロジェクトに追加するのも忘れないようにしましょう。

準備ができたので描画します。 CALayer.contents プロパティに CGImageRef を渡すと後は全部勝手にやってくれます。簡単でしょ?
- (void)viewDidLoad {
    // UIImage* 型のプロパティ self.image があると仮定して・・・
    CALayer *l = [CALayer layer];
    l.contents = self.image.CGImage;
    l.position = CGPointMake(255, 255) // l.position はデフォルトではレイヤー中央の座標になります
    [self.view.layer addSubLayer:l];
}
CGContextDrawRect のような Core Graphics (Quartz) の関数を使用すると、座標系が左下基準になるため、 UIImage を描画する際にコンテキストの上下を反転してやらないと画像が上下反対に表示されてしまう問題がありますが、 CALayer はこの座標系の差異も勝手に考慮に入れてくれるので、 UIImage の CGImage プロパティをそのまま渡すだけでよく、ラクチンです。


■アニメーションしてみる

もともと CALayer は Core Animation フレームワークのクラスですから、当然アニメーションにも対応しています。といいますか、何もしないでそのまま CALayer のプロパティを変えると勝手にアニメーションします。 http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/AnimatingLayers.html の Animation > Implicit Animation あたりに記載されている内容がそれです。
// 既にこの l が addSubLayer されている場合、
// これだけで勝手にアニメーションします
l.transform = CGAffineTransformCreateScale(1.1, 1.1);
が、ときどきこのアニメーションが邪魔になる場合があります。そんなときは
// CATransaction というクラスを使用して, 一時的にレイヤーのアニメーションを切ります
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
// CATransaction というクラスを使用して, 一時的にレイヤーのアニメーションの時間を変化させます
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
                 forKey:kCATransactionAnimationDuration];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
こんな具合でアニメーションを調整できます。


■まとめ

たったのこれだけで画面上に高速でアニメーションもできる画像を描画することができます。お絵かきソフトなどで、画面上にユーザーが任意の画像を挿入できるようにしたい、と言ったときに大変役立ちますのでおすすめです。是非試してみてください。

2010年7月10日土曜日

virtualenv が上手く動作しない場合は -p オプションと --distribute オプションを試す

Python Hackathon という変態の巣窟に来ています。 virtualenv と buildout のハンズオンを受けているのですが、いくつか詰まった点があったのでメモ。

■virtualenv
普通にインストールするとき(ベースとなるpythonのsite-packageを受け継がないようにする場合)は以下のようにします。
python virtualenv.py --no-site-package myenv
ですがこの方法ではMac OS X 10.6付属のPython 2.6.1ではエラーになってしまいました。
New python executable in foo/bin/python
ERROR: The executable foo/bin/python is not functioning
ERROR: It thinks sys.prefix is '/System/Library/Frameworks/Python.framework/Versions/2.6' (should be '/private/tmp/virtualenv-1.4.3/foo')
ERROR: virtualenv is not compatible with this system or executable
そこで https://bitbucket.org/ianb/virtualenv/issue/17/virtualenv-not-working-with-default-python-on-mac-os を参考にして回避策を探してみたところ、
python virtualenv.py --no-site-package -p /usr/bin/python myenv
のようにして-pオプションを使いベースとなるPythonのパスを明示的に指定することで上手い具合にインストールできました。


■buildout
buildoutのbootstrap.pyをダウンロードしてきて実行すればよいのですが、Mac OS X 10.6付属のPython 2.6.1のsetuptoolsはバージョンが低い(0.6c9)ため、以下のようなエラーが出てしまうことがあります。
akisute server$ python bootstrap.py 
The required version of setuptools (>=0.6c11) is not available, and
can't be installed while this script is running. Please install
a more recent version first, using 'easy_install -U setuptools'.

(Currently using setuptools 0.6c9 (/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python))
もちろんsetuptoolsのバージョンを上げてもいいのですが、あまりデフォルトの環境を汚したくありません。そこで--distributeオプションを使用します。
python bootstrap.py --distribute
こうすると、distributeというsetuptools上位互換のモジュールが自動的にbuildout環境下にインストールされ、その結果上手い具合にインストールに成功します。buildout環境下へのインストールなのでデフォルトの環境は汚れません。すばらしい。

2010年7月7日水曜日

Core Graphics (Quartz) のみで日本語文字列を描画するライブラリのヘッダファイルを書いてみた

とある理由で UIKit の描画機能が使えず、 Core Graphics のみで文字列の描画処理を行わなければならないことになってしまったので、適当に調べてみました。


■ことのはじめ

Core Graphics の機能だけで日本語の文字列を描画する方法については、既に先人の方々が調べて記事にまとめてくださっていたので、そちらを見ていただければ大丈夫です。
http://iphone-dev.g.hatena.ne.jp/ktakayama/20100129
http://d.hatena.ne.jp/r_kurain/20100316
基本的にはこちらで紹介されている方法に従って進めていけば困ることはありません。ただし、描画した文字が上下反対になることがありますので、 CGContextSetTextMatrix を使う箇所を調整したりする必要があるかも。

で、最大の問題になるのがこれらの記事で紹介されている CGFontGetGlyphsForUnichars と呼ばれる関数です。この関数を使えば楽に日本語文字列を文字化けすることなく描画することができるのですが、あろうことかこの関数はプライベートAPIであり、使うとリジェクトされてしまうらしいです。ということで別の作戦をとらなければなりません。

上記の記事にいろいろな対処方法が載っているのですが、私は一番単純に
http://www.mexircus.com/codes/GlyphDrawing.mm
というライブラリを自分のアプリに組み込む方法を試してみることにしました。この方法を使えば、 CGFontGetGlyphsForUnichars でまず作って動くようにしてから、 CMFontGetGlyphsForUnichars と書き換えるだけでそのまま動作するので楽です。

問題はこのライブラリ、mmファイルしか用意されていないのでそのままではうまく使えません。適当にヘッダファイルを用意してやる必要があります。


■と言うわけで書いたもの

と言うわけで GlyphDrawing.h を書いてみました。
http://gist.github.com/466297

ライセンスは元の GlyphDrawing.mm に準じますが、 GlyphDrawing.mm のライセンスが不明なので、不安ならばまず作者さん (http://mexircus.com/blog/) に一言聞いてみるのがいいと思います。


■使い方
  1. http://gist.github.com/466297http://www.mexircus.com/codes/GlyphDrawing.mm をダウンロードします。
  2. GlyphDrawing.mm のインポートを以下のように書き換えます:
    //#import <Foundation/Foundation.h>
    #import "GlyphDrawing.h"
  3. GlyphDrawing.hGlyphDrawing.mm をプロジェクトに追加してビルドします。
  4. CGFontGetGlyphsForUnichars の代わりに CMFontGetGlyphsForUnichars を呼びだせばOKです。


■実際に使ったコードの例
NSString *message = @"本日は快晴なり";

// フォントを設定
// ついでにmessageのサイズも取得
// ただしsizeWithFontは UIGraphics の機能なので、本当に Core Graphics だけで描画したいならここで使ってはいけません
UIFont *font = [UIFont fontWithName:@"HiraKakuProN-W6" size:32.0];
CGRect messageRect = [message sizeWithFont:font];
CGFontRef fontRef = CGFontCreateWithFontName((CFStringRef)font.fontName);
CGContextSetFont(c, fontRef);
CGContextSetFontSize(c, font.pointSize);

// Glyphを作成
size_t length = [message length];
CGGlyph glyphs[length];
UniChar chars[length];
[message getCharacters:chars range:NSMakeRange(0, length)];
CMFontGetGlyphsForUnichars(fontRef, chars, glyphs, length);

// 文字列が上下反対になるのを防止する
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, -1.0);
CGContextSetTextMatrix(c, transform);
CGContextTranslateCTM(c, 0, messageRect.size.height/2);

// 描画
CGFloat x = 100.0f;
CGFloat y = 100.0f;
CGContextShowGlyphsAtPoint(c, x, y, glyphs, length);

2010年6月26日土曜日

別スレッドで NSURLConnection を使うときのメモ

http://twitter.com/griffin_stewie/status/17022582070

@griffin_stewieさんにご指摘いただいたので調べてみました。

別スレッドでNSURLConnectionを使おうとすると、そのままではNSRunLoopのモードの問題なのか、上手い具合にデータを受信することが出来ません。私はメインスレッドでNSURLConnectionを動かすようにして難を逃れたのですが、それ以外にも教えていただいた方法を用いて、以下のように別スレッドのNSRunLoopのモードを変更して対応すると良いみたいです。
    NSURLRequest *theRequest = [NSURLRequest requestWithURL:iTunesURL];
// create the connection with the request and start loading the data
rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed
// to the context for streaming parsing. The handler structure defined above will be used for all the parsing.
// The second argument, self, will be passed as user data to each of the SAX handlers. The last three arguments
// are left blank to avoid creating a tree in memory.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
runMode:beforeDateを使うわけですね。なるほど。

2010年6月20日日曜日

iPad を紙の代わりにするのに最適なアプリとスタイラスを探してみた



iPad を購入された皆さんが苦労されているのが「iPad の使い方を探す」事だと思います。私の場合は iPad を購入したら紙のシステム手帳を鞄から取り除いてしまいたいと考えていました。そのためには、カレンダーや連絡先はともかく、手書き機能が必要です。そこで購入直後からいろいろなアプリとスタイラスを買って(場合によっては作って)試行錯誤し、ようやくある程度の結論が出せたのでご紹介してみようと思います。

2011/05/29追記 - 記事全体の構造を再編成して、より分かりやすく、現状に即した形に書き換えました。私がこの記事を書き始めたころと比べ、スタイラスの性能もアプリの性能も飛躍的に向上し、どれを選んでもほぼ間違いないレベルにまで進化いたしましたので、そろそろまとめにして最終更新にするつもりです。
2011/02/01追記 - パワーサポート スマートペンを追記。
2010/10/10追記 - oStylusのレビューへのリンクを張りました。


実際に手持ちのスタイラスで書き比べてみた記事がこちら: http://akisute.com/2011/05/iphone-ipad-2.html
Bamboo Stylusのレビューはこちら: http://akisute.com/2011/05/ipad-bamboo-stylus.html
oStylusのレビューはこちら: http://akisute.com/2010/10/ipad-ostylus.html


■まず最初に結論

機能性、入手容易性、使い勝手、値段などを考慮した結果、2011/05/29現在私がオススメするアプリとスタイラスは以下のものです。それでは個別に見ていきます。


■アプリ編

手書きアプリには大きく分けて以下の三種類があると思っています。
  1. ノート型手書きアプリ
    • 紙のノートの代わりに使うための手書きアプリです。実際のノートのように背景に罫線が入っていたりする場合もあります。
    • 機能が絞られており、特にペン先は一種類しかない場合が多いですが、その代わり実際にペンで書いているかのような美しい線が書けます。
    • 拡大縮小は限定的にしかできません。
    • 見た目に非常にこだわっているアプリが多いです。
  2. お絵かき型手書きアプリ
    • キャンバス全体のスクロールや拡大縮小ができる手書きアプリです。どちらかというと、紙のキャンバスやホワイトボードを置き換えるアプリです。
    • 図形や絵を書くのに適しています。
    • 絵を描くのに必要な機能が多いです。たとえば色が多いとか、フィルタがついているとか、レイヤが使えるとか、ブラシの種類が多いとか。
  3. その他、独創的なもの
    • 全く新しい使い方を提案するアプリです。
    • 7notesのように手書き自動認識をしたり、Instavizのようにグラフを自動で作ってくれたりするものがあります。
ここでは私自身が紙のノートとホワイトボードの代わりを欲しているということで、キーボードでタイプする機能を持っているアプリについては除外しており、純粋に手書き機能のみで、かつ私が実際に試してみたことのある 1. と 2. の一部についてのみご紹介いたします。あらかじめご了承ください。

neu.Notes - お絵かき型

  • 良い点は、なんといっても無料で、ホワイトボードの代わりをするのに必要な機能は全てある。書き味も悪くない。
  • 悪い点は、デザインがイマイチ、ノートのページが増えてくるとだんだん重くなってきて書き味が劣ってくる。
これだけ使えるアプリが無料というのが何か間違ってる気がしますが、それぐらい素晴らしいです。とりあえず入れておいて損はありません。

Adobe Ideas - お絵かき型

  • 良い点は、neu.Notesよりも綺麗な書き味と使っていてしっくりくる美しいインターフェース。レイヤーも使える。
  • 悪い点は、高い(特に日本のストアでは)、ノートの整理が全く出来ないため数が増えてくると真っ先に破綻する、外部連携が弱い。
高いのが問題ですが、使っていてneu.Notesとどちらがイライラしないかと問われると、やはりこのAdobe Ideasですね。

Penultimate - ノート型

  • 良い点は、シンプルで必要最小限の機能だけを含んでいるところ。優れた書き味、比較的安い値段。
  • 悪い点は、シンプルすぎて出来ることが少ない、(ズームが一切出来ないのが致命的)、ノートの整理がイマイチ、ここ最近のアップデートの内容がイマイチで良い方向にアプリが改善される気配がない、外部連携が弱い。
昔はノートと言えばPenultimateというぐらい良かったんですが、最近は後述するNoteshelfやNoteTaker HDが見事なぐらい成長してしまったため、もはや過去の遺物という印象です。安いぐらいしかメリットがないです。

Noteshelf - ノート型

  • 良い点は、美しいUI、必要な機能は全てある上でよけいな物がないシンプルさ、ズームが出来て紙のノートと同じように細かく書ける、Penultimateと同等レベルの優れた書き味、そして何より外部連携が神。Dropboxは当然として、Evernoteにも連携可能。書いたノートをPDF形式で選択したページだけEvernoteにアップロードなどと言うことが余裕で出来る。あとはEvernoteにOCRしてもらってあとから検索する・・・
  • 悪い点は、 正直見当たらない。 いや本当に。自分のやりたいことが今のところ全部出来ているし、非常に安定していて1ノートに画像込みで40ページ以上追加しても性能劣化やクラッシュが発生しない。紙のノートみたいに200ページぐらいのノートを作っても大丈夫そうな勢いがある。
あの孫正義が絶賛!とか紹介に書いてあって超うさんくせぇと思っていたのですが、すみません、私が間違っておりました。本当にこのアプリは素晴らしいです。紙のノートを一冊買うぐらいなら、今すぐこちらを買ってください。

ここでご紹介した以外にも、たとえば むげんメモ ですとか Note Taker HD といったアプリが面白いと思いますが、実際に使っていないので評価は控えさせていただきます><


■スタイラス編

2011/05/29編集 - 近況に合わせて完全に書き直し。

スタイラスについては、時代別に見ていきます。

原始時代(2008年~iPad発売前後)

この時代に存在したiPhone用スタイラスと言えば、ほぼ全てが先端が黒い平らなゴム状になっているスタイラスです。例を挙げればこのような商品です。
http://www.ray-out.co.jp/products/t1pen1/

残念ながら魚肉ソーセージの方がマシなので、何を間違っても絶対に買わないでください。これらの原始時代スタイラスは、導電性の低い素材で出来た平らな先端を、無理矢理押しつけてタップすることしか考えられていないため、手書きは不可能です。

さすがに最近は第二世代が登場してきたおかげで、ほぼ市場から駆逐されたようです。

第一世代(iPad発売~iPad発売後9ヶ月程度)

とまぁiPadが発売されるまではほとんど暗黒時代だったのですが、iPadが登場してきたあたりから私が第一世代と呼んでいるレベルのスタイラスが登場し始めました。すなわち、
  • 何でもいいからとにかく引きずって手書きできる先端
  • とりあえずまともに使えるレベルの導電性
を持っているスタイラスです。主に先端が導電性スポンジで出来ているスポンジ型と、筆のようになっているブラシ型が主流でした。中には変わり者で先端が金属で出来ている物もあります。これでとりあえず手書きは出来るようになりましたが、第一世代のスタイラスは値段が高く、世間一般にあまり流通していないためAmazonなどで気軽に買えず、その割には第二世代と比べて性能が悪すぎるので、今はもう忘れて結構だと思います。一応列挙すると、
  • Pogo Sketch - 第一世代の中で飛び抜けて優秀だったスタイラス。導電性以外は今でもほぼ完璧
  • oStylus - 金属を使うことで素晴らしい導電性を確保したスタイラス
などがあります。

第二世代(2011年~)

そして2011年になってついに転機が。パワーサポート スマートペン PBJ-9Xシリーズの登場を皮切りに、第二世代と呼ぶにふさわしいスタイラスが次々と登場し始めました。

第二世代の特徴は、
  • 日本のAmazonで3000円以内で余裕で買えるため入手しやすい
  • 触れるだけで反応する、極めて高い導電性と感度
  • 普通のペンと全く同じように引きずって手書きできるスムースなペン先
などを兼ね備えているところで、ようやく誰でもいいスタイラスを簡単に入手できるようになりました。この世代のスタイラスはほぼ全て中空ドーム状の柔らかいシリコンをペン先に採用しているため、一目でわかります。

以下、実際に買って試したことがあるものを挙げてみます。

パワーサポート スマートペン PBJ-9Xシリーズ
  • 良い点は、驚異的な先端感度と、安めの値段。本体が軽いのも魅力。
  • 悪い点は、先端の強度にやや不安があること、柔らかくて太いため正確に書けず、少し引っかかりがあること。
おそらく最初に出てきた第二世代のiPhone/iPad用スタイラスだと思います。今でもまったく見劣りしません。今では他にもっと安い商品もあるようですが、500円程度しか変わりませんし、あまり神経質にならなくても良いかと思います。

Wacom Bamboo Stylus
  • 良い点は、細くてスムーズなペン先、本物のペンのような筆感。
  • 悪い点は、やや重い本体と、多少感度の悪い先端。
詳細なレビューはhttp://akisute.com/2011/05/ipad-bamboo-stylus.htmlをご覧ください。高いだけあって良いです。

プリンストンテクノロジー iPad/iPhone/iPod touch専用タッチペン(ブラック) PIP-TP2B
  • 良い点は、十分な感度と、スマートペンより固くしっかりしていて書きやすいペン先。軽くて疲れない軸。値段も相当安い。ストラップを通すための穴がある。
  • 悪い点は、軸が短い。女性にはちょうどいいかもしれませんが、ただでさえ軸が短めな上にクリップも大きいので、手の大きい人には不向き。
ちょうどパワーサポートのスマートペンとBamboo Stylusの中間のようなペン先です。軽くて感度がよいあたりはBambooより優れており、ペン先が固くてふにゃふにゃしないのがスマートペンより優れていて、さらに安いのがメリット。一方でペン軸が短め(Bamboo Stylusと比べると1cmも短い)という別の問題もあり、他の製品と比べて一長一短ありますが、オススメです。

その他にも最近ではなどがあるようですが、これらの商品は実際に買って試せていないのでここでは評価を控えさせていただきます><

2010年6月19日土曜日

NSOperation を使って外部 API から非同期に結果を取得してみる

iPhone / iPad のアプリを作っていると、頻繁に登場するのが「外部 API を HTTP 経由で実行して結果を XML / JSON で取得し、それを解析してモデルクラスに変換してデータ構造に突っ込む」パターンです。当然たくさんの先人の皆様がすでに効率的なライブラリを作成されているのですが、あえて私も車輪の再発明に挑戦してみました。今回使用したのは NSOperation クラスです。 NSURLConnection クラスとデリゲートを使うだけでも簡単に非同期通信を実現することができるのですが、さらに NSOperation クラスと NSOperationQueue を使うことでさらにタスク間の依存関係を簡単に設定できたり、タスクの並列度を簡単に制御したりできそうなので、挑戦してみました。

2010/12/29追加: 発展版をASIHTTPRequestを使って作成してみました。


■実装コードと使い方

http://gist.github.com/441620

これが基底クラスとなる FetchOperation クラスです。使用する際にはこのクラスのサブクラスを作成して、以下のように parseResponseBody: メソッドをオーバーライドします。
@interface FetchMyAPI : FetchOperation
@end

@implementation FetchMyAPI
- (void)parseResponseBody:(NSData *)bodyData {
  NSString *string = [[[NSString alloc] initWithBytes:[bodyData bytes] length:[bodyData length] encoding:NSUTF8Encoding] autorelease];
  JSON *json = [MYJSONLibrary jsonFromString:string];
  // json を解析してモデルクラスに突っ込むなど・・・
  // parseResponseBody: メソッドはtry-catchで囲まれており、さらに Core Data Managed Object Context に対して lock をかけています
}
@end
あとはこのサブクラスのインスタンスを生成して、requet プロパティに任意の NSURLRequest を渡して、普通の NSOperation を使うときのように KVO を使って状態を監視すればOK。
FetchOperation *operation = [[[FetchMYAPI alloc] init] autorelease];
operation.request = myAPIRequest;
[operation addObserver:self forKeyPath:@"isCancelled" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
[operation addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
[appDelegate.operationQueue addOperation:operation];
もちろん NSOperation のサブクラスなので、 FetchOperation 同士に依存関係をつけるのも簡単です。たとえばログインAPIを実行した後にゲットユーザーAPIを実行したい場合には、
[getUserOperation addDependency:loginAPIOperation];
などとして、おなじ NSOperationQueue に突っ込んでやるだけで、自動的にログインAPI→ゲットユーザーAPIの順に並列実行してくれます。超便利です。

上記コードは基本的にご自由にお使い頂いて結構です。ただし、そのままコピペしても NSOperationQueue がなかったり Core Data Managed Object Context がなかったりで、まず間違いなく動かないと思います。適当にご自身の環境に合わせて調整していただければ幸いです。テストは結構行っているので多分大丈夫だと思うのですが、バグとか残っているかもしれません。何かあっても責任は持てません、ごめんなさい><


■NSOperation クラスのサブクラスの作り方

さてここからは実装のお話です。基本的には iPhone SDK についてくるリファレンスの NSOperation のページに全ての注意事項が載っているため、こちらを注意深く読んで実装すれば難しいことはありません(全部英語ですが><)。

最初に覚えておくべき知識として、 NSOperation クラスには二通りの実行モードが有ります。 以下、iPhone OS 3以下での挙動です。 iOS 4以降は挙動が変わり、Mac OS X 10.6以降と同じ挙動になります。
  • 非並列実行モード
    • isConcurrent プロパティが NO を返すとき、またはiOS 4以降は isConcurrent プロパティの値に関係なく常にこちらのモードになります
    • NSOperationQueue クラスが自動的に新スレッドを1本作って、そこで実行してくれるので、なにも考えなくても並列処理になります
    • main メソッドの実行が完了したら自動的に処理が完了したとみなされます
  • 並列実行モード
    • isConcurrent プロパティが YES を返すとき
    • NSOperationQueue クラスは自分のいるスレッドで NSOperation を実行します。そのため、 NSOperation 側で並列処理を行わないとスレッドが固まります
    • main メソッドの実行が完了しても処理完了とはみなされません。自分で明示的に処理が終わったことを通知できるようにする必要があります
簡単に実装できるのは非並列実行モードなのですが、これで実装するためには NSURLConnection を同期モードで実行する必要があります。で、困ったことに NSURLConnection の同期モードはひどい実装で処理も遅ければメモリもムダ食いするしタイムアウトの時間すら自分で決められないという有様なので、まったく使えません。そのため今回はやむを得ず並列実行モードを頑張って実装しました。

並列実行モードを自分で実装する手順は以下の通り。

まず何はなくとも isConcurrent で YES を返すようにします。
- (BOOL)isConcurrent {
    return YES;
}
続いて以下の4つの状態通知メソッドを実装します。これはコピペでOKですが、ready, executing, finished, cancelledは自分で宣言してください。
- (void)setReady:(BOOL)b {
        if (ready != b) {
                [self willChangeValueForKey:@"isReady"];
                ready = b;
                [self didChangeValueForKey:@"isReady"];
        }
}
- (void)setExecuting:(BOOL)b {
        if (executing != b) {
                [self willChangeValueForKey:@"isExecuting"];
                executing = b;
                [self didChangeValueForKey:@"isExecuting"];
        }
}
- (void)setFinished:(BOOL)b {
        if (finished != b) {
                [self willChangeValueForKey:@"isFinished"];
                finished = b;
                [self didChangeValueForKey:@"isFinished"];
        }
}
- (void)setCancelled:(BOOL)b {
        if (cancelled != b) {
                [self willChangeValueForKey:@"isCancelled"];
                cancelled = b;
                [self didChangeValueForKey:@"isCancelled"];
        }
}
次に start メソッドを適当に実装します。
- (void)start {
        // Follows the behavior of NSOperation in Mac OS 10.6 (and iPhone OS 3.0)
        // また、start前にrequestがセットされていない場合にも例外を発生させる
        if (finished || cancelled) {
                [self cancel];
                return;
        }
        if (!ready || executing || !request) {
                @throw NSInvalidArgumentException;
        }

        // ここで適当に事前処理を行う
        // ここは同期実行でいいです、すぐ終わるなら

        // main実行直前に現在の状態をexecutingにする
        [self setReady:NO];
        [self setExecuting:YES];
        [self setFinished:NO];
        [self setCancelled:NO];

        // mainを実行する
        [self main];
}
最後に主処理を main メソッドに書きます。 main メソッド自身か、または main メソッドの大部分は並列実行されるようにしてください。
主処理が完了したら以下のように自身の状態を更新すればOKです:
[self setReady:NO];
[self setExecuting:NO];
[self setFinished:YES];
[self setCancelled:NO];
必要に応じて cancel メソッドも実装します
- (void)cancel {
        // ここで自身の主処理をキャンセルする

        // 現在の状態をcancelにする
        [self setReady:NO];
        [self setExecuting:NO];
        [self setFinished:YES];
        [self setCancelled:YES];
}


■FetchOperationの解説

以下のような順番で処理が進みます。
  1. FetchOperation のオブジェクトが生成される (init)
  2. FetchOperation のオブジェクトが NSOperationQueue に追加される (start)
  3. 並列実行モードで FetchOperation が実行され、 start メソッドが main メソッドを呼び出す (main)
  4. main メソッドの中で NSURLConnection が非同期実行される (main)
  5. NSURLConnection がレスポンスを受け取って、データを受信する (connection:didReceiveResponse:)
  6. NSURLConnection がすべてのデータを受信する (connectionDidFinishLoading:)
  7. NSInvocationOperation クラスを使って受信したデータの解析処理を並列実行する (parseMain:)
  8. parseMain: の終了を監視して、終了し次第 FetchOperation の実行を完了する (observeValueForKeyPath:ofObject:change:context:)
大きく分けると、1〜3が事前準備、4〜6が通信部、7〜8が解析部になっています。7〜8は NSInvocationOperation を使ってお手軽に並列実行していますが、別の NSOperation にしたほうがより良い実装になると思います。現実装のように NSInvocationOperation を使っていると、何かの間違いで FetchOperation 自身に複数のスレッドからのアクセスが発生して状態が壊れてしまう恐れがあります。

isConcurrent プロパティで YES を返すようにしているため、 FetchOperation 自身は NSOperationQueue が置いてあるスレッドと同じスレッドから実行されます。なので、実際に並列実行されるのは NSURLConnection が通信をしている部分と、 parseResponseBody の中だけです。それ以外の部分で重い処理を実行すると思いっきり固まるのでご注意ください。

2010年5月30日日曜日

Core Data のパフォーマンスをちょっとだけ調べてみた

ちょっと仕事で触ってみて分かった範囲のことを書きます。断りがない限り、 iPhone 3GS で Wifi 接続環境下においてテストしました。

■キャッシュ無し vs キャッシュ有り

executeFetchRequest:error: メソッドを用いて、 Entityのプロパティで一件だけ絞り込んで返すようなクエリは大変遅いということが分かりました。Indexを付けて実行してもほとんど速くなりません。どうやらそもそもバックエンドに使っているSqliteが大変遅い、特にコネクションを生成したり破棄したりするのが遅い感じがするので、ループで一件ずつ取得するなどのときはたくさんのSQLが実行されないようにする必要があります。 objectWithID: メソッドは試していないのでちょっと不明です。

回避策として、アプリが起動したタイミングで当該エンティティの全オブジェクトをあらかじめ取ってきて、 NSMutableDictionary にでも突っ込んでおく。次回以降のフェッチはその NSMutableDictionary から行う、と言うようにすると凄く速くなりました。

実測値は以下の通り。
pre loading time というのが自前のNSDictionaryキャッシュの事前生成、parseResponseBodyというのがXMLの解析で、この中に大量のCore DataオブジェクトをDBから引っ張ってくる処理が含まれています。
//////////////////
プリキャッシュなし
//////////////////

2010-05-25 12:11:37.254  FetchAllModelA parseResponseBody time : -1.214856
2010-05-25 12:11:39.674  FetchAllModelB parseResponseBody time : -2.416063
2010-05-25 12:11:41.097  FetchAllModelC parseResponseBody time : -1.384185

//////////////////
プリキャッシュあり
//////////////////

2010-05-25 14:12:14.788  pre loading time : -0.039540
2010-05-25 14:12:17.518  FetchAllModelA parseResponseBody completed. time : -0.836754
2010-05-25 14:12:20.719  FetchAllModelB parseResponseBody completed. time : -1.312910
2010-05-25 14:12:19.169  FetchAllModelC parseResponseBody completed. time : -0.902832
ほんの0.03秒のプリキャッシュ処理のおかげで、XML解析が最大で1秒以上短縮できています。とにかくCore DataがSQLを飛ばさないように調整すると効果があるみたいです。


■DB書き込み速度

NSManagedObject の生成はメモリ上 (NSManagedObjectContext) で行われるためなかなか高速なのですが、それを save するのがとにかく iPhone 3G で顕著に遅く、 300件程度のデータを保存するのに5秒以上かかってアプリが正常終了できないという事態が発生しました。リレーション張りすぎたかなーと思います>< iPhone 3GS なら2秒3秒程度で間違いなく完了するので特に問題になっていません。対策としては暇なときに逐一DBにsaveするか、どうでもいいデータはiPhone 3Gでは保存しないとか、一時エンティティにするとか。

iPhone の NSURLRequest で gzip 使ってみた

http://stackoverflow.com/questions/2682483/nsurlconnection-nsurlrequest-gzip-support

こちらに記載があるとおり、 Cocoa の NSURLConnection はgzip圧縮されたコンテンツを自動的に解凍して扱ってくれるみたいです。gzipでリクエストを受け取るには、以下のように NSURLRequest のヘッダに値を追加します。
[urlReq setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
これだけで後はすべて自動的にやってくれます。もちろんサーバー側がきちんとgzip圧縮して返すように設定されていなければなりません。


■パフォーマンスを試してみる

サーバーと通信を行い、レスポンスとして1000行程度のXMLを取得して解析するアプリで実験してみました。iPhone 3GS, Wifi回線を使用。

圧縮無しの時の結果はこちら。
API①
16:11:58.822 開始
16:12:04.126 レスポンス受信
16:12:04.184 データ受信
16:12:05.322 パース完了

API②
16:11:58.838 開始
16:12:04.118 レスポンス受信
16:12:04.433 データ受信
16:12:07.276 パース完了

API③
16:11:58.847 開始
16:12:04.718 レスポンス受信
16:12:05.822 データ受信
16:12:08.299 パース完了
圧縮ありの時の結果はこちら。
API①
16:03:43.056 開始
16:03:46.349 レスポンス受信
16:03:46.414 データ受信
16:03:48.473 パース完了

API②
16:03:43.080 開始
16:03:46.326 レスポンス受信
16:03:46.671 データ受信
16:03:49.905 パース完了

API③
16:03:43.088 開始
16:03:46.308 レスポンス受信
16:03:46.371 データ受信
16:03:47.532 パース完了
一回しか試していないのでムラはありそうですが、通信自体は確実に早くなっている気がします。3Gで計測すればさらに顕著な違いになると思うのでおすすめです。

iPad 3G を Xcode につないだら対応していない OS と言われてしまった件



昨日の勉強会でさっそく買ったばかりのiPad 3GをXcodeにつないでみたら、ご覧のように対応していないOSであるとエラーが出てしまいました。

再度iPhone Developer CenterからSDKをダウンロードしてインストールし直したら問題なく動作するようになりました。おそらく単にSDKが古かっただけなのだとは思いますが、一応。

第 2 回西東京 iPhone 勉強会に行ってきました

http://atnd.org/events/4014

メンツがそうそうたるメンバーでびっくりしました。内容も非常に充実していて、 iPhone 技術ネタとしては Bonjour と UIGestureRecognizer 、iPhone 技術以外のネタとしては iPhone アプリのネタだしの仕方と iPhone アプリで受託案件をした際のお話、最後に HTML5 で Keynote みたいなのを作ったよと言う話でした。

技術ネタは予習しておいたおかげで、聞くだけになってしまわなくてすごくよかったです。 UIGestureRecognizer は UIView.multiTouchEnabled を無視して複数指のジェスチャが取れるというネタが大収穫。あとは UIWebView のタッチを無理矢理検出する方法とか。

個人的には iPhone アプリで受託案件をした際の経験談が非常に面白かったです。作る方はまだ分かるのですが、契約だとかお金の話だとかそういう話になってくるとなかなか参考になるものがないので、非常に貴重でした。次回もこのネタは聞きたいです。

HTML5 は可能性を見た感じです。マルチタッチも検出できているし簡単なアニメーションも CSS で実装できているみたいで、 JavaScript (笑)なんて言えなくなりそう。

予想通りというか iPad 率は6割超えてました。 iPhone アプリリリース経験者も5割以上。

最後に懇親会で iPadによる iよせがき を試してみました。はい、単に岸川さん高山さんにiPad上にサインを貰っただけです>< ありがとうございます!

2010年5月24日月曜日

Core Data では モデルの delete - insert をしない方が良い

最近お仕事で Core Data を頻繁に使っているのですが、ちょっとだけハマったケースをご紹介します。 Core Data ではモデルの delete - insert による更新をしない方が良いようです。


■delete - insert が問題になるケース

たとえば、 Core Data から全く同じ内容をフェッチして表示する二つのUITableViewがあるとします。それぞれ A および B と名付けます。
ここで、 A を表示してから、 B を表示する際に Core Data モデルを delete - insert すると、 A を再度表示した際にアプリケーションがクラッシュします。原因はおそらく、 A で既に読み込まれているモデルを B が削除してしまっているため、 A で既に削除されているモデルをテーブルが表示しようとしてクラッシュしているものだと思われます。 A の viewWillAppear などのタイミングで、再度 Core Data からオブジェクトを取得し直せばこの問題は解決します。

そもそも NSFetchedResultsController を使えばたぶんこの問題は発生しないと思うのですが、いずれにせよ変更があったデータをフェッチしなおさなければならなくなってしまいます。ということで可能な限りプライマリキー項目を設定して update で対応することをお勧めします。ただし update は update で sqlite3 の実行がやたら遅いということが分かっているのでこちらはこちらで別途チューニングが必要です。詳細はまた別エントリーにて。

2010年5月4日火曜日

Byline 3 ヘルプ と Tips

Byline 3 のちょっとした日本語ヘルプとTipsをこちらに掲載しようと思います。

※2010/05/11よりBylineの日本公式アカウント @bylinejp を開設しましたので、是非ご活用ください。


■メインフォルダって何?

設定画面の同期セクションにあるメインフォルダという設定。これは、簡単に言えば Bylineで表示するフォルダ です。デフォルトでは「すべてのアイテム」になっています。この状態だと、 Google Reader 側にあるすべてのフォルダがBylineと同期されて画面に表示されます。メインフォルダを設定することで、 Google Reader 側の指定されたフォルダのみをBylineと同期して表示する事ができます。一部のフィードのみを同期したい場合に有効です。

注意点として、 一度に指定できるメインフォルダは一つだけ です。


■Webページのキャッシュ設定について

設定画面のキャッシュセクションにあるWebページという設定。こちらの内容が少々難解になっているので解説したいと思います。


  • モバイル向けデザイン
    この設定をONにすると、外部Webサービスを使用して対象のWebページのコンテンツを「モバイル向けデザイン」に変換してキャッシュします。モバイル向けデザインにすると、CSSが除去されたり、イメージが除去されたりリサイズされたりします。また、全体的に縦長なデザインになり、モバイルデバイスで読みやすい形式になります。
  • 自動でキャッシュするコンテンツ
    メインフォルダ、スター付きアイテム、メモのうち、同期時に自動的にキャッシュするものを選択します。画像はメインフォルダが「すべてのアイテム」にセットされている場合の物ですが、メインフォルダを変更するとここの名前も変化します。この設定項目をONにした場合、以下のようなポリシーでキャッシュが行われます。
    • メインフォルダに含まれるフィードは、次の「フィードごとのキャッシュ設定」で設定した内容に従ってキャッシュされます。
    • スター付きアイテムに含まれるフィードは、すべてのフィードがキャッシュされます。
    • メモに含まれるフィードは、すべてのフィードがキャッシュされます。


  • フィードごとのキャッシュ設定
    メインフォルダに含まれるフィードのキャッシュポリシーを設定します。 この設定が適用されるのはメインフォルダに含まれるフィードのみです。 スター付きアイテムおよびメモには適用されず、常にすべてのフィードがキャッシュされます。
    一覧にある各フィード名をタップすると、以下の三つの状態が順番に変化します。
    • 無印(デフォルト状態)
      本文が意図的に削られていると判断されたアイテムのみを自動的にキャッシュします。すべての本文が含まれているアイテムはキャッシュしません。
      本文が意図的に削られているアイテムというのは、たとえば このような ニュース本文全体を乗せない RSS フィードの記事のことです。
    • √マーク付き
      本文が削られているか否かに関わらず、常にキャッシュします。
    • ×マーク付き
      本文が削られているか否かに関わらず、常にキャッシュしません。
    また、右側の図にある薄い色の√マークがついているフィードは、「Bylineによって本文が意図的に削られていると判断されたフィード」であることを示します。この薄い色の√マークはメインフォルダを自動でキャッシュする設定にしているときにのみ表示されます。左側の図のCNET Japanにはチェックが無くて右側の図のCNET Japanにはチェックがあるのはこれが原因です。


■Twitter連携の設定方法

BylineではOAuthを使用してTwitterアカウントにログインします。残念ながらこのOAuthの画面が英語のままになっているため、こちらで代わりまして簡単に解説いたします。



設定画面のアカウントセクションにあるTwitter欄をタップすると、上のような画面になります。ユーザー名とパスワードを入力して、ログインボタンを押してください。



この画面が正常に表示されれば完了です。画面右上の「完了」をタップして終了してください。なんか下の方で Redirecting you back to Byline とか表示されちゃってますが無視してOKです。


■Twitter投稿時にURLを短縮する方法

Twitter投稿画面には一見、URLを短縮するためのボタンがありません。ですがご安心ください。BylineにはURLを自動的に短縮する機能があります。



このように普通に本文をタイプして、140文字よりも長くなってしまうと・・・



ご覧の通り!自動的にURLが短縮されます。



さらに本文が長くなってそれでも投稿できなくなってしまったらこのようになります。どうでしょうか、いちいちボタンを押して縮めるよりスマートだと思います。


■裏技:Byline 2と同じ感覚で「すべて既読にする」方法

Byline 3ではUIが変更されて、Byline 2ではアイテム一覧の一番下にあった「すべて既読にする」ボタンがありません。個人的にはこの変更はすごい抵抗があって作者の人にも何とかならないかとお願いしてみたのですが、その代わりにちょっとした裏技を付けてくれました。



画面一番下のツールバーをスワイプすると、ツールバーに「すべて既読にする」ボタンが表示されて、すべて既読にすることができます。これで多少はByline 2に近い感覚で操作できるかと思います。