2009年10月24日土曜日

libHaruを使ってiPhoneアプリからPDFを作成する

ある日突然、iPhoneからPDFファイルを作ったら面白そうだと思ったので、早速試してみることにしました。既に先駆者の方がいらっしゃるかと思ったのですが、調べてもそれらしい資料が無いので自力でなんとかしてみることにしました。需要はあまり無いかと思いますが、何かのお役に立てば幸いです。


■PDFファイルを生成する方法
まずはPDFファイルを生成する方法を知らなければ話になりません。ということで、調査しました。
  • 一からライブラリを自作する
    jspdf(http://code.google.com/p/jspdf/)などという漢気あふれるライブラリがあるのですから、やってやれないことはない!と思い、さっそくPDFの仕様書(http://www.adobe.com/devnet/pdf/)をAdobe社からダウンロードしてきたのですが、31MB, 500ページ以上あります。なにこれこわい。
  • Adobe PDF Library(http://www.adobe.com/devnet/pdf/library/ http://www.est.co.jp/pdfl/index.html
    本家のライブラリです。日本ではイースト社がライセンスしてるみたいですが、どう見ても有償です。チェンジお願いします。
  • libHaru(http://libharu.org/wiki/Main_Page
    こうして散々探した挙句ついに見つけた至高のPDFライブラリがこちら。C言語で書かれており、iPhoneでもバッチリ動きそうです!もちろんオープンソース!素晴らしい!
ということで、今回はlibHaruを使ってみようと思います。


■libHaruをXcodeでビルドする
1.libHaruのソースコードをダウンロードしてXcodeプロジェクトに追加
最新のソースコードをhttp://libharu.org/wiki/Downloadsからダウンロードしてきて解凍します。このまま自分のMacで使えるようにするだけなら. configureしてmakeしてmake installすれば一発なのですが、あいにくそれではiPhoneで動かすことが出来ません。
includeディレクトリにヘッダファイルが、srcディレクトリにCソースコードが入ってますので、これら全部を自分のXcodeプロジェクトにコピーして取りこみます。


2.libpngのソースコードをダウンロードしてXcodeプロジェクトに追加
libHaruを用いてPDFに画像を出力したいときには、別途libpngが必要になります。そこで、libpngも一緒にビルドすることにします。libpngのソースコードはhttp://www.libpng.org/pub/png/libpng.htmlから入手できますので、これをダウンロードしてきてXcodeに追加してビルドすればよいのですが、ここではもっと簡単で確実に動く方法をご紹介します。そう、「既にlibpngをビルドして使っている他のiPhoneプロジェクトからパクる」です!(もちろん、きちんとライセンスに違反していないことを確認しましょう)
おあつらえ向きなことに、cocos2d for iPhoneの0.8.1以降(http://code.google.com/p/cocos2d-iphone/source/browse/#svn/trunk)では内部的にlibpngを使用しています。これを真似しない手はありません。さっそくcocos2d for iPhoneの最新ソースを取得し、Xcodeでプロジェクトを開いて、Support/external/libpng以下のソースをコピーして自分のプロジェクトに追加しましょう。


3.libHaruをconfigureする
. configureで動けばいいのですが、多分. configureしても今の自分のMacの環境に合わせてConfigureされてしまい、iPhoneでは動かないだろうと思ったので、自分の手でconfigureすることにしました。といっても、直すのは一箇所だけです。includeディレクトリに入っていたhpdf_config.h.inファイルを以下のように修正します。
/* include/hpdf_config.h.in.  Generated from configure.in by autoheader.  */

/* Define to 1 if you have the header file. */
#undef HAVE_DLFCN_H
#define HAVE_DLFCN_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_INTTYPES_H
#define HAVE_INTTYPES_H 1

/* Define to 1 if you have the `png' library (-lpng). */
#undef HAVE_LIBPNG
#define HAVE_LIBPNG 1

/* Define to 1 if you have the `z' library (-lz). */
#undef HAVE_LIBZ
#define HAVE_LIBZ 1

/* Define to 1 if you have the header file. */
#undef HAVE_MEMORY_H
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDINT_H
#define HAVE_STDINT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDLIB_H
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRINGS_H
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRING_H
#define HAVE_STRING_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_STAT_H
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_TYPES_H
#define HAVE_SYS_TYPES_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_UNISTD_H
#define HAVE_UNISTD_H 1

/* debug build */
#undef HPDF_DEBUG
#ifdef DEBUG
#define HPDF_DEBUG 1
#endif

/* debug trace enabled */
#undef HPDF_DEBUG_TRACE
#ifdef DEBUG
#define HPDF_DEBUG_TRACE 1
#endif

/* libpng is not available */
#undef HPDF_NOPNGLIB

/* zlib is not available */
#undef HPDF_NOZLIB

/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

/* Define to the full name of this package. */
#undef PACKAGE_NAME

/* Define to the full name and version of this package. */
#undef PACKAGE_STRING

/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME

/* Define to the version of this package. */
#undef PACKAGE_VERSION

/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
#define STDC_HEADERS 1

/* Define to `unsigned int' if does not define. */
/* #undef size_t */
書き換えたら、ファイル名をhpdf_config.hに変更します。


4.Xcodeから新規ビルドターゲットを作成する
ソースコードの準備が出来たら、次はソースコードをビルドしてlibharu.aとlibpng.aを生成するためのビルドターゲットを新規に作成します。ビルドターゲットは使い方を覚えると非常に便利です。




最初にlibpngのビルドターゲットを作ります。libpngのビルドターゲットは、2.でご紹介したとおりcocos2d for iPhoneの最新ソースのXcodeプロジェクト内に既に用意されていますので、これを真似して作るとよいです。ただし、libpngをビルドする際にzlib.dylibが必要になりますので、自分のプロジェクトにzlib.dylibを追加するようにしておいてください。最初からiPhone SDKの一部として用意されています。



続いてlibHaruのビルドターゲットを作成します。こんな感じです。



最後に、libharu.aを自分のiPhoneプロジェクトの依存ライブラリに追加します。


5.レッツコンバイン!
これでビルドする準備が整いましたので、いよいよビルドします。
緊張の一瞬!Cmd+Bをプログラムドライブ!!



できました!!!!


■Hello, libHaru!
無事にビルドが完了いたしましたので、次はいよいよPDFを生成してファイルに出力してみます。
// TEST CODE: testing libharu
NSString *path = nil;
const char *pathCString = NULL;
NSLog(@"[libharu] PDF Creation START");
HPDF_Doc pdf = HPDF_New(NULL, NULL);
NSLog(@"[libharu] Adding page 1");
HPDF_Page page1 = HPDF_AddPage(pdf);
NSLog(@"[libharu] SetSize page 1");
HPDF_Page_SetSize(page1, HPDF_PAGE_SIZE_A4, HPDF_PAGE_LANDSCAPE);
NSLog(@"[libharu] TextOut page 1");
HPDF_Page_BeginText(page1);
HPDF_UseJPFonts (pdf);
HPDF_UseJPEncodings (pdf);
HPDF_Font fontEn = HPDF_GetFont(pdf, "Helvetica", "StandardEncoding");
HPDF_Font fontJp = HPDF_GetFont(pdf, "MS-Mincyo", "90ms-RKSJ-H");
HPDF_Page_SetFontAndSize(page1, fontEn, 16.0);
HPDF_Page_TextOut(page1, 100.00, 100.00, "Hello libHaru!");
HPDF_Page_SetFontAndSize(page1, fontJp, 16.0);
HPDF_Page_TextOut(page1, 100.00, 60.00, [@"はろー日本語" cStringUsingEncoding:NSShiftJISStringEncoding]);
HPDF_Page_EndText(page1);
NSLog(@"[libharu] Path drawing page 1");
HPDF_Page_SetLineWidth(page1, 4.0);
HPDF_Page_SetRGBStroke(page1, 1.0, 0, 0);
HPDF_Page_Rectangle(page1, 200, 200, 40, 150);
HPDF_Page_Stroke(page1);
NSLog(@"[libharu] PNG image drawing page 1");
path = [[NSBundle mainBundle] pathForResource:@"portrait2"
ofType:@"png"];
pathCString = [path cStringUsingEncoding:NSASCIIStringEncoding];
NSLog(@"[libharu] LoadPngImageFromFile path:%@\n pathCString:%s", path, pathCString);
HPDF_Image image = HPDF_LoadPngImageFromFile(pdf, pathCString);
HPDF_Page_DrawImage(page1, image, 300, 50, 245, 319);

// Get documents directory
NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
path = [arrayPaths objectAtIndex:0];
path = [path stringByAppendingPathComponent:@"test.pdf"];
pathCString = [path cStringUsingEncoding:1];
NSLog(@"[libharu] SaveToFile path:%@\n pathCString:%s", path, pathCString);
HPDF_SaveToFile(pdf, pathCString);
NSLog(@"[libharu] Freeing PDF object ");
HPDF_Free(pdf);
NSLog(@"[libharu] PDF Creation END");

これを実行すると、アプリのDocumentディレクトリにtest.pdfが生成されるはずです。


■実機で試してみる
シミュレータでは動くようになりましたが、実機で動かなければ何の意味もありません。ということで、いよいよ実機でテストしてみます!生成したPDFファイルを表示するためのUIWebViewを作成し、実機で表示してみた結果がこちら。



Hooray!!


■さて次は
いよいよ次からが本番、UIViewの中身をPDFに変換して出力する機能を作ってみようと思います!

2009年10月23日金曜日

iPhone向けに最適化されたPNGをlibpngで扱う方法

メモ。
iPhoneアプリのバンドルに同梱したPNGファイルは、ビルド時に最適化処理が行われてしまうため、そのままではlibpngで読み込むことが出来ません。iPhone向けに最適化されたPNGをlibpngで扱う方法は、いまのところ二つ。


1:最初から最適化をしないようにする
参考にしたページはこちら。
http://d.hatena.ne.jp/wasabi-arts/20090301/1235856525


このように、IPHONE_OPTIMIZE_OPTIONS=-skip-PNGsを追加するか、


またはこのように、最初っから圧縮をしないように設定する。


2:最適化したファイルをいったんUIImageで読み出して、再度PNGでファイル書きだしする
いったんUIImageを使って当該ファイルをロードして、ファイルにpng形式で書き出せばよいらしいです。
(@nakamura001さんありがとうございます!)

[2009/10/24 22:20追記]
@nakamura001さんがご自身のブログで検証結果をアップされてます。
http://d.hatena.ne.jp/nakamura001/20091024/1256371800

UIButtonからPNG画像を抜く方法ですが、これを応用すればUIButtonに最適化されたPNGを使用していても普通のPNG画像を取得することが出来ます。私の環境でもテストしたところ、うまくいきました!

2009年10月17日土曜日

PythonのEvernote APIをProxyに対応させることに成功しました

前回記事で見事大失敗したので、今回改めてThriftのTHttpClientのPython実装にProxy機能を持たせるチャレンジを行いました。結果、無事成功しました!さっそく共有させていただきたいと思います。


■PythonのEvernote APIをProxyに対応させる方法
まずはEvernote APIの中で使用されている通信クラス、thrift.transport.THttpClientを修正する必要があります。
以下のようにTHttpClient.pyを修正してください(または別名ファイルとして保存してください)。

http://gist.github.com/204501

修正しましたら、APIを呼びだす側のコードを以下のように変更してください。
# これを・・・
# import thrift.transport.THttpClient as THttpClient
# こうする
import thrift.transport.MyTHttpClient as THttpClient
import evernote.edam.userstore.UserStore as UserStore
import evernote.edam.userstore.constants as UserStoreConstants
import evernote.edam.notestore.NoteStore as NoteStore
import evernote.edam.type.ttypes as Types

##########
# 中略
##########

# プロキシを使わなくて良いときはproxy引数を無視すればよい
# userStoreHttpClient = THttpClient.THttpClient(userStoreUri)
# プロキシを使いたいときはこうする
userStoreHttpClient = THttpClient.THttpClient(userStoreUri, proxy="myproxy.example.com:8080")
userStoreProtocol = TBinaryProtocol.TBinaryProtocol(userStoreHttpClient)
userStore = UserStore.Client(userStoreProtocol)

versionOK = userStore.checkVersion(applicationClientNameString,
                                   UserStoreConstants.EDAM_VERSION_MAJOR,
                                   UserStoreConstants.EDAM_VERSION_MINOR)
これだけで通信時にプロキシを介してくれるようになりました!


■何をやっているか
そもそもThriftのTHttpClientはプロキシを使うこと自体が全く想定されていない?作りになってまして、他の言語の実装でもすべてプロキシは使えるようになっていません。たとえばC#での実装はこちら(http://issues.apache.org/jira/secure/attachment/12391517/THttpClient.cs)ですが、これもプロキシは使えません。connection.Proxy = null;ってなってます。

というわけで、勝手に自分でTHttpClientを再実装してプロキシを使えるようにしています。あんまりよい方法ではないと思いますので、本番サーバーなどでは真似しないでください><


■前回からの改良点
前回はhttplib.HTTPを使うのを辞めて、httplib.HTTPConnectionクラスを使うようにしたのですが、コレがそもそもの大失敗でした。といいますか、httplib.HTTPのソースを見たら内部でばっちりhttplib.HTTPConnectionが使われてるじゃないですかorz
self.__http = httplib.HTTP(self.host, self.port)
self.__http._conn #これでhttplib.HTTPConnectionが取得できる
そして前回最大の失敗がread()メソッドが呼ばれるたびに接続をcloseしてしまっていたところです。httplib.HTTPの実装をみてようやく気づいたのですが、ここでは接続をCloseしてはいけないんですね・・・
これらを踏まえて、今回はTHttpClientの中でhttplib.HTTPをそのまま使うようにして解決しました。

他にもhttplib.HTTPResponseは以下のようにしてファイルポインタが取得できるとか
response = conn.getresponse()
response.fp # これがファイルポインタ。httplib.HTTP.read()でも使われている
httplibはとにかくドキュメントに書いてない仕様が多すぎです><

2009年10月11日日曜日

UIViewControllerのtouchesBeganとかtouchesEndedが上手く機能しなかったと思ったら・・・

ひさっびさに普通のUIKitを使ったiPhoneアプリを作ったりし始めたらかなりの範囲を忘れてしまっていて大ハマりしてます。中でも一番困ったのがこれ。
@implementation AbesiViewController

- (void)viewDidLoad
{
[super viewDidLoad];
self.wantsFullScreenLayout = YES;
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

- (void)dealloc
{
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesBegan");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesMoved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesEnded");
}

@end
とまぁ、なんの変哲もないUIViewControllerにタッチを扱うためのイベントハンドラを搭載してみただけなのですが、コレがまぁ動かない動かない!

こういうときに考えられる原因は、だいたいがInterface BuilderでInteraction Enabledのチェックを入れ忘れているとか、ViewControllerとViewをつなぎ忘れているとかそんなのばっかりなので真っ先に調べてみました。が、やはり問題は見つかりません。

試しにUIViewControllerではなくUIViewにtouchesBeganを載せてみるとコレが動くんです。ああ、OS 3.1.2ぐらいから挙動が変わったのかなとか思っていたら、

http://stackoverflow.com/questions/1025574/uiviewcontroller-not-receiving-touchesbegan-message
OK, I'm a dummy. It works fine. The problem was, I didn't realize I was sending a release message to the UIViewController without having retained it elsewhere first. So that was causing the problem.
あーーー!そうだ!!!UIViewControllerをretainしてない!!!orz

retainしたら解決しました。ほんと腕がなまってる・・・

2009年10月8日木曜日

PythonのEvernote APIをプロキシに対応させようとして挫折した

Evernote APIで使用されているThriftのTHttpClientクラスには、プロキシの設定を行うためのインターフェースが用意されていません。そのため、プロキシ越しにEvernote APIの呼び出しを行うことが出来ないみたいです。
(ひょっとしたら出来るのかもしれませんが、調べた範囲ではわからず)

そこでThriftのTHttpClientクラスのPython実装をプロキシに対応させるべくチャレンジしてみました。

http://gist.github.com/204501

基本的には元々httplib.HTTPで実装されていたところをhttplib.HTTPConnectionに変えただけなのですが、ところがこれがうまくいかない。このクライアントで/edam/user/にCheckVersionリクエストを投げると500エラーが返却されてしまいます。で、500エラーを拾うと再度試行するようになっているのか、あえなく無限ループに突入><
プロキシの設定が間違っているのかと思い、試しにプロキシを外してデバッグログをはかせた結果がこちら。
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\nHost: sandbox.evernote.com\r\nContent-Type: application/x-thrift\r\nContent-Length: 82\r\n\r\n'
send: '\x80\x01\x00\x01\x00\x00\x00\x0ccheckVersion\x00\x00\x00\x00\x0b\x00\x01\x00\x00\x00(Python URL to Evernote/0.1; Python/2.5.2\x06\x00\x02\x00\x01\x06\x00\x03\x00\r\x00'
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: Apache-Coyote/1.1
header: Content-Type: application/x-thrift
header: Content-Length: 29
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
read start
read POST request
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\n\r\n'
read getresponse
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 500 \r\n'
header: Server: Apache-Coyote/1.1
header: ETag: W/"3401-1254794526000"
header: Last-Modified: Tue, 06 Oct 2009 02:02:06 GMT
header: Content-Type: text/html
header: Content-Length: 3401
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
header: Connection: close
read start
read POST request
うーん、HTTPSの実装が間違っているのか、リクエストパラメータが間違っているのか。1回目のリクエストはうまくいくけど2回目のリクエストが失敗しがちみたいです。もう少し調査してみます。


あ!今気づいた!!
2回目のリクエスト時(readメソッド実行時)のHTTPヘッダがめちゃくちゃだ!!ここを直せば動くかも。

2009年10月5日月曜日

Evernote APIためしてみた


API Keyくださいとお願いしたらすぐに発行してもらえたので、試しにEvernote APIを触ってみました。


■準備
http://blog.lilyx.net/2008/11/03/evernote-api/を見ると発行するときに手間がかかったとの記述がありましたが、私の場合は半日で発行されました。まぁ1年たってますし、状況が良くなったのかもしれませんね。申請時にアプリの詳細を事細かに書いたのが良かったのかもしれません。


■Hello Worldを動かす
まず最初にsandbox環境に自分用のアカウントを作成します。http://sandbox.evernote.com/login.jspにアクセスして、いつも通りアカウントを作成。忘れがちなので注意です。
アカウントが出来たら、ダウンロードしてきたSDKに最初っからサンプルスクリプトがついてきているので、こちらを実行するだけです。今回はPythonのサンプルスクリプトを試してみました。
cd sample/python
cp * ../../lib/python
cd ../../lib/python
python EDAMTest.py username password


■ENMLを試してみる
今回の最終目的は「はてブみたいにURLだけ渡したら勝手にEvernoteに取り込んでくれる」ことなので、まずはどのぐらい気軽にHTMLを取り込めるかを調べてみました。Evernoteの本文はENMLというHTMLに似たマークアップ言語で記述されています。
http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272588
こちらの記載を読んでみたところ、かなーーーーーり面倒くさいということが判明。htmlタグやbodyタグが含められないのはまだいいとして、id属性もclass属性も許可されません。さらに困ったことに・・・

pタグ閉じてないからダメー。


brタグ閉じてないからダメー。

さすがXML、厳しい。どうやらHTMLとは全く互換性がないと考えた方が良さそうです。
HTMLをENMLに変換するライブラリとかないかなー?

ENMLは死ぬほど面倒ですが、それ以外のところは今のところ簡単です。

2009年10月4日日曜日

cocos2dでParticleSystemを使うときのメモ

懲りずにcocos2dで遊んでます。今回はParticleSystemを触ったときに気づいたことなどをメモしてみます。


■ParticleSystemが放出するParticleを回転させたい
http://www.cocos2d-iphone.org/api-ref/latest-stable/interface_particle_system.html
こちらの公式リファレンスには2009/10/02現在記載されていませんが、startSpin, startSpinVar, endSpin, endSpinVarというプロパティが用意されており、こちらの値を調整することで放出される個々のParticleに回転を与えることが出来ます。
        // angle of particles
startSpin = 90;
startSpinVar = 0;
endSpin = 1800;
endSpinVar = 3600;
注意点として、PointParticleSystemを継承したParticleSystemでは、spin系のプロパティは利用できないみたいです。パフォーマンスで劣るそうですが、QuadParticleSystemを継承するようにしましょう。

結果はこんな感じ。青いミミズみたいなのがParticleSystemから放出しているParticleです。
回転の中心軸の位置がおかしいのかな?


■Particleの大きさを変化させたくない
endSizeにkParticleStartSizeEqualToEndSizeを指定すると、放出されたParticleの大きさが変化しなくなります。
        // size, in pixels
startSize = 90.0f;
startSizeVar = 30.0f;
endSize = kParticleStartSizeEqualToEndSize;
endSizeVar = 30.0f;


■応用:Particleの回転を変化させたくない
現在のcocos2dではまだサポートされていませんので、適当にParticleSystem.mのコードを書き換えます。具体的には180行目のあたりを以下のようにします。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (endSpin == kParticleStartSpinEqualToEndSpin )
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
これで放出されたParticleがくるくる回転するのを防げます。でもこのマジックナンバーを使った実装、正直イマイチです。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (spinFixed)
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
個人的にはこの方に実装するほうが好きです。パフォーマンスは悪いかも。

各種WebサービスのAPI認証方法を調べてみた

自分でWeb サービスを作る際に、APIの認証ってどうやって作ればよいのだろうと思い立ち、各種Web サービスのAPIの認証方法を調べてみました。


■Google
参考にしたページはこちら。
http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI
認証方法
ユーザーIDおよびパスワードを元に、Cookieを生成
認証時のAPI通信
HTTPS POST
認証URL
https://www.google.com/accounts/ClientLogin
認証後のAPI通信
HTTP GET/POST, HTTPS GET/POST
トークン送出方法
HTTPリクエストヘッダのCookie属性に「SID」を含める
Cookieを使う方法ですので、Webブラウザを用いるWebアプリケーションに対する認証の場合は非常に簡単ですが、クライアントアプリケーションの場合は、認証URLのレスポンスを元にSIDを保存し、HTTPリクエストヘッダのCookie属性にSIDを追加する処理を自分で行う必要があるため、ちょっとだけ面倒です。それでもシンプルで分かりやすい方法だと思います。


■Remember the Milk(RTM)
参考にしたページはこちら。
http://www.rememberthemilk.com/services/api/authentication.rtm
http://www.rememberthemilk.com/services/api/methods/rtm.auth.getFrob.rtm
認証方法
APIキー、開発者とサーバー間の共通鍵、使い捨てセッションの3つを元に、セッションIDを生成
認証時のAPI通信
HTTP GET
認証URL1(使い捨てセッションの取得)
http://api.rememberthemilk.com/services/rest/?method=rtm.auth.getFrob&api_key=abc123
認証URL2(本認証)
http://www.rememberthemilk.com/services/auth/?api_key=abc123&perms=delete&frob=123456&api_sig=zxy987
認証URL2(認証済みセッションIDの取得)
http://api.rememberthemilk.com/services/rest/?method=rtm.auth.getToken&api_key=abc123&frob=123456

認証後のAPI通信
HTTP GET
トークン送出方法
HTTPパラメータに「auth_token」を含める
Webアプリケーションだけではなくクライアントアプリケーションへの対応を行っているためか、先ほどのGoogleのAPIと比べて格段に難しくなります。その代わり、HTTPS POSTを用いなくても、HTTP GETのみで認証を行うことが出来ます。こういうのをRESTって言うんでしょうか?正直良く分からず。
以下、クライアントアプリケーションでの認証手順。

1.APIキー申請をすると、以下の2つの値がもらえる
api_key・・・開発者がサーバーからもらえる公開鍵。公開鍵なので外に漏れても良い。
shared secret・・・開発者とサーバーが持つ共通鍵。絶対に外に漏れてはならない。

2.api_keyを元に、使い捨てセッションを生成する
frob・・・使い捨てセッション。以降、認証手続きの間のみ利用する。
frobの意味についてはhttp://en.wikipedia.org/wiki/Frobを参照。

3.shared secretと認証URLのリクエストパラメータを元に、api_sigを生成する。
認証URLには以下の3つのリクエストパラメータがつきます。
api_key=abc123&perms=delete&frob=123456
これを記号を抜いて結合して、
api_keyabc123permsdeletefrob123456

先頭にshared secretをつけて、
BANANASapi_keyabc123permsdeletefrob123456

md5 hash値を計算します。計算結果がapi_sigになります。
md5('BANANASapi_keyabc123permsdeletefrob123456')

4.api_key, frob, api_sigの値を元に、ユーザー認証画面を開いてIDとパスワードを入力してもらう
ここで認証URL2をユーザーにブラウザで開いてもらって、IDとパスワードを入力してもらえば認証が完了します。

5.認証が完了したセッションIDを取得する
auth_token・・・セッションID。次以降のリクエストには、全てこのauth_tokenをHTTPリクエストパラメータとして含める。


■Evernote
参考にしたページはこちら。
http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272584
認証方法
メールアドレス、パスワード、consumerKey, consumerSecretを元に、セッションIDを生成
認証時のAPI通信
Thrift TBinaryProtocol wrapping a THttpClient transport
認証URL
https://www.evernote.com/edam/user
認証後のAPI通信
Thrift
トークン送出方法
UserStore Authentication Token?を付与する
Thriftというフレームワークが使われているようです。Thriftの実装がC, Java, PHP, Python, Perl, Rubyなどで用意されていて、クライアントはこれらの実装を用いて認証すればよいらしいです。詳細は良く分かりませんが、こちらもAPI Keyと共通鍵を利用しているため、比較的RTMの認証に近いことをしているように見えます。
ちなみに上記はクライアントアプリケーション用の認証で、Webアプリケーション用の認証にはOAuthを使用しています。OAuthについてはまた別の機会に調べます。


■Twitter
参考にしたページはこちら。
http://usy.jp/twitter/index.php?Twitter%20API
http://twitter.pbworks.com/API%20Docs#Authorization
http://watcher.moe-nifty.com/memo/2007/04/twitter_api.html
認証方法1
ユーザーID、パスワードを利用したBasic認証
認証時のAPI通信
なし
認証URL
なし
認証後のAPI通信
HTTP GET
トークン送出方法
http://username:password@twitter.com/のようにしてユーザーIDとパスワードを送出する
認証方法2
ユーザーIDおよびパスワードを元に、Cookieを生成
認証時のAPI通信
HTTP POST(HTTPSは無い?)
認証URL
http://twitter.com/login
認証後のAPI通信
HTTP GET
トークン送出方法
HTTPリクエストヘッダのCookie属性に「_twitter_session」を含める
認証方法3
OAuth
詳細については不明
以下、http://watcher.moe-nifty.com/memo/2007/04/twitter_api.htmlから転載。

public_timeline の取得等一部の API を除くほとんどの API で、認証を使用する。応答に protected なユーザに関する情報が含まれる可能性のある API は認証が必須となっている。
現在、OAuth認証とBASIC認証が使用可能。

トークンベースの認証 OAuth は、今のところベータ版であるが、将来、OAuth 認証を標準的な認証方法にする予定。
BASIC認証で使用するユーザ名はメールアドレスまたはスクリーン名のどちらでも構わない。

Webブラウザ等を経由して Twitter にログインしたときに発行される cookie を使うことで BASIC 認証の代わりにすることもできるが、公式には cookie を使っての API 実行はサポートしない。
(訳者による注記: API によっては cookie 使用時も BASIC 認証が要求されるものもある。また、BASIC認証での API 実行と cookie を利用しての API 実行では、異なる結果が返る API もある。特に、protected なユーザに関する挙動に違いが見られる。
OAuth 認証時も API の応答に cookie が含まれるが、次回 API 実行時にこの cookie をサーバーへ送り返す必要はない)。

もうこれを見るだけでばっちりでした。

2009年9月25日金曜日

スティーブ・ジョブズが目の前なう






2009/09/25: 帰宅したらきちんとした写真アップします。
2009/09/28: 写真アップしました。

■これまでのあらすじ
アメリカ旅行1日目
アメリカ旅行2日目〜3日目


■まずはGoogleへ
今日はCaltrainに乗ってシリコンバレーへ向かいます。お目当てはGoogleとAppleの本社!

Caltrainのサンフランシスコ駅へ到着。日本の駅と作りが全然違ったり、標準軌道(広軌?)だったり、全2階建ての客車をディーゼルの機関車が引っ張る構成だったり、そんな機関車がずらりと横一列に並んでいる姿が拝めたりと、鉄道マニアにはたまりませんよ。あ、私は鉄道マニアなんかじゃないんだからねっ?><

一路San Antonio駅へ。ここからVTAのバス40番に乗り換えて一路Googleへ移動。
駅で降りてまず感じたのが、空が広い!明るい!緑が鮮やかで綺麗!暖かい!家並みが美しい!まるで絵に描いた楽園のような風景です。なるほど、これはすごい。みんなシリコンバレーで起業したがるわけです。

Google到着。めっちゃめっちゃ広い!!><
雰囲気としては途中Palo Alto駅すぐそばにあるスタンフォード大学のキャンパスとそっくりで、全然「会社」って感じがしません。緑がたくさんあって、へんちくりんな形の建物がどーんと。大学のキャンパスかカフェか何かみたいです。

さんざん周りをうろついて写真を撮りまくったあげく、何かの間違いで中に入れてもらえないかとVisitor用のロビーに向かったら、受付のおねーさんに「ここはClosed Campusです、知り合いが面会者がいない奴は立ち入り禁止です。でてけ」とやんわり怒られ、外に出たらセキュリティのお兄さんにまたやんわり怒られ、あえなく撤退。入れないのはわかっていたもののやはりショック。

"There are no public visitor center or a shop"だそうです。嘘つき!俺は知ってるよ!某氏がGoogle社内見学したとき、お土産にバッグ買って帰ってきたのを!

あ、そうか、だから"public"って断りを入れたのね。というわけで、技術にはオープンですが、本社警備には大変クローズドなGoogleでした><


■続いてAppleへ
ふたたびCaltrainに乗って、こんどはSunnyvale駅へ。

駅からVTAバス55番で南下するとAppleの本社に到着。外見はGoogleと違ってテクノロジー企業らしい企業ビルって感じです。あたりの交差点にたくさんリンゴマークの住所書きがあります。

先ほどのGoogleでの失態を教訓に、まずはすぐに施設に近づかずInfinity Loopの公道からゆっくり観察することに。あ、6 Infinity Loopに山積みのMac Proがある。一つお土産に欲しい。

Infinity Loopを左回りに進むと、6, 5, 4, 3, ....という風に施設が並んでいます。一番最後が1 Infinity Loop, 本社ビルです。

本社ビル前に到着。お土産屋さん発見。観光バスと観光客らしき人も発見。どうやら思ったより観光客に対してオープンになっているみたいです。

しかし当たり前ながら本社ビルの中には入れないようで、入り口の前で立ち往生。


■あんびりーばぶる!
で、中にも入れないし写真も撮ったしそろそろ帰るかと思っていたら。

http://twitter.com/akisutesama/status/4354424273
http://twitter.com/akisutesama/status/4354453168
http://twitter.com/akisutesama/status/4354498099

まさかのジョブズ降臨!!

目の前3メートルの位置にジョブズが!声をかけたけど無視されてすたすた行ってしまわれた。

いや待て、本物がこんなド真ん前を堂々と歩くわけがない、偽物か影武者だと思って売店のおねーさんに聞いてみたら、"then it might have been"(ならたぶん本物だったのよ)とのお返事が。あと"He usually doesn't answer because he's so busy"らしいので、どうやら本物っぽいです。ああ、来て良かった!

最後に記念撮影してたら建物の中が写るからそこで撮るなとセキュリティのイケメン兄ちゃんにやんわり怒られました。またか!><


■じゃあ帰りましょ
Sunnyvaleの駅前で食ったsushiはなかなかおいしかった。カリフォルニアロール、言うほど不味くないですよ。私の舌がゲテモノ食いなのかもしれませんけど。ただし握りはいまいち、しゃりがなんだか変。食えないことはないけど、近所のスーパーのパック寿司のほうがおいしいと思う。これで4ドルは日本だとぼったくり。

帰りのPalo Alto駅で乗ってきたスタンフォードの学生が目の前でMacBook広げてプログラミングを始めたので興味本位でのぞいてみた。ソースはよく見られなかったけど、たぶんAdobe AirかFlash。集中力が凄くって、乗ってからサンフランシスコの駅についてドアが開いてもまだMacとにらめっこしていた。きっと朝から夜遅く(19時)まで授業があっただろうにと思うと、さすがスタンフォードの学生だと感心するとともに、自分も負けていられないと思うのでした。

2009年9月24日木曜日

アルカトラズなう

1日目に引き続き、2〜3日目。


■2日目
朝起きてまずは近所の散策。Whole Food MarketとWalgreensを発見。

ジャパンタウンへ行ってみるがあまりにも閑散としていておもしろくないので即撤退。伊藤園の充実野菜が売ってあった。

おもむろに目の前を走っていたMUNIバスに興味本位で乗ってみたら降り方がわからず大変苦労した。運ちゃんに降り方を聞いてようやく降りたらなぜかシビックセンターについた。

シビックセンター見学

ダウンタウンへ移動

ダウンタウン見学、Apple StoreでWifi電波を借りたりFallout 3の実物大Power Suitにつられて地元のゲーム店によってみたり。DSとWiiのソフトが日本と全然違う。ほかはにたりよったり

あ、Walgreensにおーいお茶(濃い味)売ってる、$1.69

ケーブルカーでフィッシャーマンズワーフへ

Pier39で飯、ようやくこっちのレストランの作法とか文化的背景がわかった

Pier33で明日のアルカトラズ行きのチケットを入手

適当にバスに乗ったらゴールデンゲートブリッジについた

ゴールデンゲートブリッジを見学。風が糞強い!めちゃ揺れる!落ちる!!ロードバイクにひき殺される!!死ぬ!!><

ゴールデンゲートウェイ公園に移動。広すぎる上に車道があって公園というイメージがしない。地元の田舎道みたいな雰囲気。

なぜかツタンカーメン展をやっていたので入ってみた

やばい、ツタンカーメン展ヤバい、超おもしろい!
日本で一般的に開催されている展覧会と違って見せ方が上手い!展示に移る前に、決められた人数ずつ展示室前の墳墓を模した暗室に誘導され、そこでディスカバリーチャンネルみたいなかっこいいムービーを見る。凄い盛り上がる!ムービーが終わったらいよいよ中へ、入ると同時にツタンカーメンの像がお出迎え!とこんな具合にストーリー性があっておもしろい。美術品を順番に置いて説明テキストおいて終わりじゃなくて、映像とか部屋の構造まで使ってストーリー性を持たせているのが、まるでディズニーのアトラクションみたいだと思った。

2時間もたってしまって19時になったのだが、外がまだ明るい。よく考えたらサマータイムのおかげでまだ日本時間では18時相当なのだった。サマータイムに生まれて初めて感謝した。

近所のWhole Food Marketで晩ご飯を買って食う。普通に食える。おいしい。誰だアメリカの飯が不味いとか言ったやつは。自分にはこれでちょうどいいぐらいだ。


■3日目
朝9時半からアルカトラズへ移動

昼14時半までアルカトラズ観光。とにかくすばらしいの一言につきる。島から見えるサンフランシスコの町並みも、刑務所跡も、とても綺麗で歴史が感じられる。ここの歴史なんてたかだか100年とちょっとしかないはずなのだが、数百年歴史があるはずの地元山口・錦帯橋よりも歴史が感じられるのは、やはり見せ方が上手いのと、音声ツアーガイドがあまりにも秀逸であるおかげだろうなぁと思った。アルカトラズ刑務所の音声ツアーガイドは本当にすばらしい!ディスカバリーチャンネルの世界を自分の足で歩いているような雰囲気がたまらない!
※帰ったら写真と動画うpします。

フィッシャーマンズワーフで遅い昼飯。ただのハンバーガー(10ドル)、これが凄くおいしい。ポテトもマックやバーキンの安物とは全然違って、30分たってもおいしい!このぐらいなら、なるほど料理と読んで差し支えないなぁ。

近所のWhole Food Marketで晩ご飯と朝ご飯を買いだめ

終了


作法や町歩きの仕方が身についてきて、英語も耳に慣れ聞き取れるようになり、おもしろくなってきました。明日はいよいよGoogleとAppleの見学に行ってきます。余裕があればStanfordも!

2009年9月22日火曜日

サンフランシスコなう

思い立ったが吉日ということで今サンフランシスコに来ております。初海外です。
  • 飛行機で9時間10時間は思ったより全然楽。ただし酔い止めと耳栓とマスクとエア枕は絶対必須。どれか一つでも欠かすと結構きつい。
  • 飛行機機内寒すぎ
  • サンフランシスコも寒すぎ(体感で東京の10月下旬以降)、長袖が必須
  • こっちの人は皆さんとっても親切、アメリカ人は意外とフレンドリーだった
  • でも何言ってるかわからん←一番困る、TOEICの点数とリスニング力は全く比例しません
  • SUBWAYの味は東京と代わらなかった(量が多いしピクルスが酸っぱくて不味いが基本同じ)
明日はダウンタウン行ってみたり町になれようかと思います。23でアルカトラズでも見て、24でGoogleでも見て帰る。

2009年9月13日日曜日

iMacもSnow Leopardにしてみた



MacBook Airに引き続き、iMacのほうもようやくSnow Leopardに乗り換えてみました。


■ユーティリティとか動くの?
USB Overdrive
動きました!一番気がかりだったので嬉しいです!
Shades
こちらも動作しました。Snow Leopardで多少マシになった輝度設定ですが、まだまだ明るすぎるので重宝しています。
ただし、画面右上のアイコンをクリックして表示されるスライダーが動きません。輝度設定を変更したいときは、システム環境設定のパネルからShadesを直接起動して変更する必要があります。まぁ、その程度なら全然OK!
MenuMeters
やっぱりダメでした。残念ですが忘れようと思います><


■その他、iMacの気になるところ直った?
画面の輝度が明るすぎる
多少マシになってます。一番最低まで下げれば、以前よりはまぶしくない(気がします、単にコントラストの設定が2.2になったせい?)。でもまだまぶしいです、引き続きバグチケットをAppleに投げてきます。
マウス
相変わらず加速度がオフに出来ない糞仕様です。反応速度自体はマシになっていますが・・・しかも困ったことに初代Macintoshからこうらしいです、伝統だとか。改善されることはなさそうです>< USB Overdrive様々。
ExposeとSpacesのマウス操作仕様
困ったことにSpacesのマウス操作が微妙に変わっています。Spacesをボタンで起動した後、開きたいSpaceを「左クリック」しない限りSpaceが切り替わらない仕様になってしまいました。いままでは「Spacesの起動ボタン」でもSpaceが切り替わっていたのですが。Exposeは従来通りなので差異が発生してます。地味に困るのでこれもバグチケット投げないと。

2009年9月8日火曜日

Mac OS X 10.6 Snow Leopardのeasy_installでPILをビルドするときに気をつけることなど

Snow LeopardにPIL(Python Imaging Library)をインストールしようとして見事にハマりましたので、後学のために事の顛末を記しておきます。


■まず最初に、調べて分かった結論
  • Snow LeopardでPythonを使うときは、デフォルトで付属しているPython 2.6.1を使用すること。
  • Snow LeopardにはPython 2.5.4も付属しているが、こちらは使用するべきではない。
  • Snow LeopardのPython 2.5.4のdistutilには不具合?があり、UnixCCompilerクラスがC言語のモジュールを64bitでビルドしてくれないため、Snow Leopardでは動作しなくなる
  • したがって、C言語のモジュールを使用しているPILはSnow LeopardのPython 2.5.4を使うとビルド出来ない
  • どうしても2.5.4を使いたいときには、環境変数にARCHFLAGS "-arch x86_64"を追加してからPILをビルドするとうまくいくかもしれない(未確認)
  • MacPortやMacPythonからのインストールは実験していないため未確認

※ あくまで2009年9月8日現在での断片的な情報です。皆様がごらんになっている時点では既にMacPort上のPythonやMacPythonも対応を完了しているかもしれませんので、参考程度にご覧ください。でもSnow Leopard上のPython 2.5.4はほんとお勧めしません。


■ことのはじまり
Leopardのころはきちんと動いていたGoogle App Engine SDKが、Snow Leopardにアップグレードしたとたん突然PILモジュールがないと警告を出すようになってしまいました。確認してみると/Library/Python/Python2.5/site-packages/以下に確かにインストールされているのですが、何度試しても警告が消えません。Pythonのバージョンも以前から使っているPython 2.5のままです。これは何かがおかしい、再インストールした方がいいだろうと判断し、http://www.pythonware.com/products/pil/から1.1.6のソースコードを落としてきていざビルド。
sudo python setup.py install
python selftest.py
・・・が、動きません。なにやらImagingMathが見つからないだのなんだのとエラーが出てしまいます。これだけではよく分からないので、直接PILのモジュールをPythonからimportしてみることにしました。
akisute PIL$ python2.5
Python 2.5.4 (r254:67916, Jul 7 2009, 23:51:24)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import Imaging
>>> # 問題なくインポートできました
>>> import _imaging
Traceback (most recent call last):
File "", line 1, in
ImportError: dlopen(./_imaging.so, 2): Symbol not found: _jpeg_resync_to_restart
Referenced from: /Library/Python/2.5/site-packages/PIL/_imaging.so
Expected in: flat namespace
in /Library/Python/2.5/site-packages/PIL/_imaging.so
>>>
ふむふむ、どうやらこの_imaging.soというのがなにやら悪さをしているようです。@tokibito先生にお尋ねしたところ、このsoファイルというのはC言語のソースをコンパイルしたライブラリで、Pythonから動的にインポートできるようにpython.hというヘッダをインクルードしてつくられているらしいということが分かりました。なるほど、それでPythonから直接C言語で書かれてビルドされたライブラリをimportできるんですね。凄いなPython。

となると怪しいのはPILのビルド工程。先ほどのエラーを見ると_jpeg_resync_to_restartというシンボルが見つからないと表示されていたため、インストールに使ったlibjpeg(MacPortよりインストール)に何か問題があったのではないかと推測したのですが、特に問題は見つからず。

次にPILのビルドログを調査。細かいwarningが出まくるのはいつものことなので無視して調べてみますが、一見何もエラーは出ていません。するとあることに気づきました。
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc build/temp.macosx-10.6-i386-2.5/_imaging.o build/
temp.macosx-10.6-i386-2.5/decode.o build/temp.macosx-10.6-i386-2.5/encode.o build/temp.macosx-10.6-i386-2.5/map.o build/
----
中略
----
-L/o
pt/local/lib -L/System/Library/Frameworks/Python.framework/Versions/2.5/lib -L/usr/lib -ljpeg -lz -o build/lib.macosx-10
.6-i386-2.5/_imaging.so
うん、なるほど、原因が分かりました。-arch i386と -arch ppcでしかビルドされていないのがまずそうですね。Snow Leopardは64bitですから、-arch x86_64を追加してビルドしなければならないはずです。

ためしにPython 2.6.1でこの_imaging.soを読み込もうとしてみた結果がこちら。
akisute PIL$ python
Python 2.6.1 (r261:67515, Jul 7 2009, 23:51:51)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _imaging
Traceback (most recent call last):
File "", line 1, in
ImportError: ./_imaging.so: no appropriate 64-bit architecture (see "man python" for running in 32-bit mode)
>>>
ビンゴ!間違いなさそうです!あとは、どうすれば64bitでビルド出来るかを調べるだけです。


■distutilとの戦い
とは言ってみたものの、そもそも一体全体どういうカラクリでpython setup.py installコマンドがgccビルドを実行しているのかが分かりません。まずはsetup.pyのソースを読んでみることにしました。するとdistutils.command.build_extなるモジュールをimportして使っていることが判明。怪しい。Python2.5のシステムライブラリをあさってみると、ありましたありました。


さっそくコードを読んでみると・・・
475         objects = self.compiler.compile(sources,
476 output_dir=self.build_temp,
477 macros=macros,
478 include_dirs=ext.include_dirs,
479 debug=self.debug,
480 extra_postargs=extra_args,
481 depends=ext.depends)
おおっといきなり発見、self.compiler.compileとか怪しさ抜群です。こいつはどこからやってきたのかとソースをたどると、なにやらunixccompiler.pyというモジュールを発見。いかにも私がコンパイラだと言わんばかり。犯人はこいつに違いない。さっそくコードを開いて-archとかで検索をかけてみましたが、i386とかppcとか直接指定している箇所は見あたりませんでした。その代わり別の収穫を発見。
def _darwin_compiler_fixup(compiler_so, cc_args):
"""
This function will strip '-isysroot PATH' and '-arch ARCH' from the
compile flags if the user has specified one them in extra_compile_flags.

This is needed because '-arch ARCH' adds another architecture to the
build, without a way to remove an architecture. Furthermore GCC will
barf if multiple '-isysroot' arguments are present.
"""
----
中略
----
if stripArch or 'ARCHFLAGS' in os.environ:
while 1:
try:
index = compiler_so.index('-arch')
# Strip this argument and the next one:
del compiler_so[index:index+2]
except ValueError:
break

if 'ARCHFLAGS' in os.environ and not stripArch:
# User specified different -arch flags in the environ,
# see also distutils.sysconfig
compiler_so = compiler_so + os.environ['ARCHFLAGS'].split()
----
後略
----
環境変数ARCHFLAGSとかいうのを見てるみたいです。なるほど、じゃこいつに"-arch x86_64"とか追加すればきちんと64bitビルドしてくれるのでしょうか?


■そしてあっけない幕切れ
ここまでなんとか一人で調査していたものの、たまりかねた隣の席の皆さんからアドバイス。

「Python 2.6じゃないのがまずいんじゃね?」

え、何、そういうこと?まぁ念のために試してみるか、ということでPythonを2.6に切り替えて再度PILをビルド。

あ、-arch x86_64がビルドオプションについてる。

あ、python selftest.py一発で通った。

あ、Google App Engine SDKがPILの警告吐かなくなった。

終了。

なにそれ。俺の苦労、何よ。


■あれ?
・・・おかしいな、今日作るはずだったGoogle App Engineのアプリ、まだ10行ぐらいしか書いてないよ><

2009年9月3日木曜日

.tmux.confをいじってtmuxのエスケープキー(prefix)を変える

~/.tmux.confというのがtmuxの設定ファイル、screenにおける~/.screenrcのようです。
制御キー(screenでいうescapeに相当)を設定するには、
set-option -g prefix C-t
unbind-key C-b
bind-key C-t send-prefix
こんな感じで書きます。C-を付ければControlキーが修飾キーになるようです。
.screenrcのときは
escape ^Tt
みたいに書けばOK。

参考にしたページはこちら:
http://ktjx.blogspot.com/2009/08/tmux.html


■トラブルシューティング
間違ってこんな設定を.tmux.confに書き込んでしまうと、
set-option -g prefix t
Tキーがprefixになってしまうためターミナルからexitquitを入力させることが出来なくなりtmuxを終了させられなくなります。Control-Cも試してみましたが無視されてしまいました・・・仕方ないので強制的にMacのTerminal.appを落として解決したのですが、問題がその後。tmuxにはセッションを保持する機能があるので、強制的にTerminalを殺してもtmuxのプロセスはまだ生き残っています。再度Terminalからtmuxを起動すると、tmuxがこの生き残っているセッションを復活させてしまうため、設定ファイルを正しく書き換えても反映されなくなってしまい泥沼に突入します。

ということで、tmuxプロセスを先にkillしてから再度tmuxを起動すれば無事に設定が反映されます。こんな恥ずかしいミスをしでかすのは私ぐらいかと思いますが、何かの拍子にお役に立てば幸いです。

Python 2.5系列では__repr__()でunicodeを返すといろいろトラブルが起きる

Django(正確にはapp engine patch)のmanage.py shellで遊んでいるとき、とあるクラスを生成すると必ずUnicodeEncodeErrorが発生していることに気づきました。具体的には以下のような感じ。
>>> from game.models import *
>>> hts = HeroTemplate.all()
>>> ht = hts.fetch(1)[0]
>>> ht.template_name #問題なし
>>> ht.name          #問題なし
>>> hero = Hero(
... name=ht.name,
... symbol=ht.symbol,
... max_life=ht.max_life,
... life=ht.max_life,
... max_move=ht.max_move,
... move=ht.max_move,
... weapon=None,
... item=None,
... )
>>> hero             #ここでUnicodeEncodeError
>>> ht.createHero()  #上記と同じ処理をやるメソッド、これもUnicodeEncodeError
原因を調べていてわかったのですが、Python 2.5系列では__repr__()がunicodeを返すようにしてしまうとトラブルの元になってしまうようです。
参考にしたサイト:
http://d.hatena.ne.jp/alisue/20090402/1238690818

たとえば、
>>> class Abesi:
...   def __repr__(self):
...     return u'¥u3059¥u305a¥u304d¥u3044¥u3061¥u308d¥u30fc'
... 
>>> abesi = Abesi()
>>> abesi #UnicodeEncodeError
これを実行するとabesiを表示しようとしたタイミングでエラーになります。環境はWindowsXP上のCygwin 1.7 + Python 2.5.4で、ターミナル上ではshift_jisを使っています。始めっからターミナルがutf-8を扱えるような環境なら__repr__()でunicodeを返しても上手くいくかもしれません。
しかしながらどこの環境でも動くとはいいがたい状態なので、
  • __str__()と__repr__()はstrを返す
  • __unicode__()はunicodeを返す
Python 2.5系列ではこのルールを守っておいたほうが無難のようです。Python 3.0からはunicodeがデフォルトになるらしいのでこんな面倒ごとをいちいち考えなくてもよいのでしょうか?いまだにPython 3.0試していませんが、ちょっと興味が湧いてきました。


■っていうかそもそも
Djangoのdjango.db.models.Modelクラスは特にオーバーライドしなくても綺麗な__repr__()を出力してくれるので、デフォルトの__repr__()を使えばよかった><

2009年9月1日火曜日

多少マシになったかな




相変わらずこんなしょぼっちい物を作っていたりします。


■前回からちょっとだけ改善
敵が出るようになりました(キャラの向き滅茶苦茶ですが)。
弾が出るようになりました(タダの自機狙い3Wayですが)。
ちょっとずつデザエモン程度には近づいてきたかなぁと。
あとは自機が動いて弾が出れば一応STGっぽくはなります。

今回作ったところまでの経過をgithubにうpしてみました。
http://github.com/akisute/MyShooting/tree/338e164b4716ec27a6a9b8d384f7714243f53d2e

最新のソースはこちら。
http://github.com/akisute/MyShooting/tree/master


■今回の気づき
弾の発射とか敵の出現タイミングをフレーム単位で計測して実行しているのですが、cocos2dのフレーム時間はかなりばらつくようです。弾が200発程度出ただけでフレーム時間が2倍になったりします。要するに弾の発射間隔が2倍になります。ちょっとこれはいただけない感じ。毎フレームごとに前フレームから何秒経過したかが取得できるので、それを元に時間単位で発射タイミングを測った方がいいかもしれません。

あとはActionがやたら重いです。何が重いってActionの動き自体よりもActionオブジェクトの作成と解放に時間がかかっている感じです。頻繁に起動が変わったり単純直線移動しかしない動きを再現するとき(たとえば高速弾や自機コントロール)は、昔ながらのvx, vyを使った方法のほうが軽くて良さそうです。

2009年8月29日土曜日

どのクラスのインスタンスを作っているかに注意しましょう・・・

突然ですが問題です。以下のソースはコンパイルできますがバグがあります。
http://github.com/akisute/MyShooting/blob/4570825e7384157d3572b689cc23f9bb3acc0b82/Classes/MSTGObjectFactory.m

さて、どこが間違っているでしょうか?




■なんて突然言ってもわからないですが
正解はこちら。
+ (MSTGObject *)cannon
{
// ここが間違い。MSTGObjectではなくてMSTGCannonのインスタンスを生成する必要がある
MSTGObject *object = [MSTGObject spriteWithFile:@"cannon.png"];
return object;
}
で、MSTGObjectはライフがデフォルトゼロなので、生成された瞬間に即死して画面から退場してくださるという悲しいバグが。うーむJavaではこんな情けないバグ作ったりはしないのですが・・・ともかくこれで一歩前進。

イニシャライザとかコンポジットとか考えた結果がこれだよ!

前回の記事から引き続きCocos2Dでちょこちょこアプリを書いてます。
ソースコードも公開してみました。
http://github.com/akisute/MyShooting/tree/master


■で、あれからどうなった
全面作り直しorz

コーディングを進めていくうちに次々と問題点が噴出してきたのですが、その中でもこれはまずいと思ったのが2点。


■問題点その1.オブジェクトリストの管理が面倒
画面への描画を担当してくれるCocosNodeにSpriteを登録するのとは別にオブジェクトを管理するためのマネージャクラスを持っていたため、オブジェクトを追加するたびにこんなコードを書かなくちゃいけなかったんです。
// MSObjectの親ノード(Layer)の中で・・・
MSObject *object = [[[MSBullet alloc] initWithPosition:ccp(100,100)
angle:0.0
speed:200.0] autorelease];
[self addChild:object.node];
[[MSObjectManager sharedInstance] addObject:object
forOwner:kMSObjectOwnerEnemy];
これだけならまだしも、今度は消すのが面倒。
[self removeChild:object.node];
[[MSObjectManager sharedInstance] removeObject:object];
どちらかを消し忘れるとエラーになりますし、ループ中にMSObjectManagerからインスタンスを消すとエラーになったりと結構散々です。この問題点1はまだCocosNodeにaddMSObject:forOwner:とかremoveMSObject:みたいなメソッドを追加すればなんとか対応できたのですが、もっと深刻な問題点2が。


■問題点その2.オブジェクトクラスの中でscheduleが使えない
これで完全に積みました。要するにこんなコードが書けないわけです。
// MSObjectの中で
// 毎フレームごとに呼び出されるメソッドを定義する
- (void)onFrame:(ccTime)dt
{
// 死亡判定してみたり
// 衝突判定してみたり
// ライフを回復したり無敵フレームを設定してみたり
}

- (id)init
{
if(self = [super initWithFile:@"ship.png"])
{
// sceduleメソッドはCocosNodeのメソッドなのでこうやって登録する
// しかしschedule出来るのはCocosNode自身が持っているメソッドのみ
// そのためこの方法でメソッドを登録しても
// 実行時に「spriteにはonFrameなんてメソッドがありません」とエラーになる
[self.sprite schedule:@selector(onFrame:)];
}
return self;
}
これを回避するにはMSObjectにスケジューラを自分で実装する必要が出てきて・・・ってそれもうCocos2dでやる意味なくね?ということで結論。Cocos2dはCocosNodeを継承して使え。


■作り直しの結果
MSObjectをコンポジットではなくSpriteのサブクラスにして解決。ついでに名前をMSTGObjectに改名しました。イニシャライザが漏れてややこしいと言う問題が発生してしまいますが、そこは以下のようにファクトリクラスを使って解決。
MSTGObject *ship = [MSTGObjectFactory ship];
MSTGObject *bullet = [MSTGObjectFactory bullet];

Macbook Air (Early 2008)にMac OS X 10.6 Snow Leopardをインストールしてみた


最初は見送ろうかと思っていたのですが、Twitterのタイムラインを眺めていると「速い!」とか「良い!」との声が多数見受けられ、居ても立っても居られず仕事帰りにさくっと買ってきてさくっとインストールしてみました。
iMacとMacBook Airの2台あるので、まずはAirのほうに試しにインストール。

(2009/08/30追記) QuickSilverが動作するようになったので追記しました。
(2009/09/13追記) 64bit Kernel起動を試してみたので追記しました。


■インストールにかかる時間
大体40分〜50分ぐらいでした。OKボタンを数回押して放置するだけ。簡単です。


■インストールしてみて第一印象
確かに「速い!」です。もっとも印象に残るのが再起動やスリープ→復帰。また、アプリの起動や終了も明らかに速くなっています。具体的にはDockの上で4回はねないと起動しなかったアプリが1回はねただけで起動したりします。

それから地味に嬉しいのが入力ソースをアプリごとに保持するオプション。10.4までは可能で10.5で廃止されたオプションですが、10.6でめでたく復活しました。QuickSilverとTextMateは英字でTweetieは日本語でとか保持してくれるのがいい感じ。
http://d.hatena.ne.jp/Psychs/20090828/1251467136

そして何よりトップにも載せましたが4本指スワイプが使えるようになりました。最近のMacBookでは当たり前でしたが、なにせ私が使っている初代MacBook Airはこのオプションが無くてずいぶん肩身の狭い思いをしていたので嬉しくてたまりません。もうこれだけのために3000円払ってもいい。


■使えて良かった開発系のツール一覧
Firefoxは完全にそのままOK。Multiclutchの設定もそのまま使えました。
XcodeとiPhone SDKは専用の物をインストールしなおしてすぐにそのままビルド出来るようになりました。こちらも問題なし。
TextMate問題なし。OmniFocusやEvernoteも問題なさそうです。DropBoxも動いています。Growlも意外なことにきちんと動作しています。日本語入力のATOK2008 for Macも大丈夫です。
最大の問題としてMacPortがバージョン1.7系だと動作しませんが、つい先ほど公式の1.8インストーラがリリースされたため、こちらからSnow Leopard用の1.8をインストールしなおし、sudo port uninstall installedしてから必要な物を再度installし直すことで復旧できました。
QuickSilverもそのままでは設定画面が壊れて表示されなくなってしまいますので、最新のβビルドをこちらのページ(http://code.google.com/p/blacktree-alchemy/downloads/list)からダウンロードして入れ直すことで無事使えるようになりました。


■使えなくなってしまったアプリ一覧
SIMBLを使っていたアプリは32ビットモードで起動しても使えなくなっているようです。たとえばTerminalColourは完全にダメ。MenuMetersもダメです。QuickSilverも起動するだけなら問題なさそうですが、設定画面を開いた瞬間にフリーズして落ちてしまいます。
幸いにしてあまりSIMBLに依存した生活をしていなかったおかげで、ほとんど被害無く過ごすことが出来ています。


■64bit kernel起動は?
そもそもMacBook Air(Early 2008)は64bitカーネルのサポート対象外なのですが、ダメ元で試してみました。
結果、Boot modeは64bitにできましたが、実際に立ち上がるカーネルは32bitになってしまいました。やっぱりダメみたいです。あと、EvernoteとFirefoxのEvernoteプラグインの挙動が変になってしまったため、すぐに戻しました。


■まとめ
普通に使っている方にはアップグレードの手間もほとんど無くいい感じです。特に初代MacBook Airをお使いの方は是非アップグレードをお勧めします。逆にSIMBLやSafariプラグインに依存した生活をお送りで、ユーティリティが動かないと困る方はしばらくアップグレードを見合わせた方がよいかもしれません。

あと各所でインストールディスクが傷だらけでインストールできないという症例が報告されておりますのでご注意ください。最悪の場合はApple Storeに持ち込めばたぶん交換してくれます。

2009年8月23日日曜日

vimのProjectプラグインまとめ

今回のPython Hackathonで大活躍してくれたProjectプラグインについて適当にまとめ。


■プラグインのダウンロードはこちらから
http://www.vim.org/scripts/script.php?script_id=69


■まずはこれを読む
http://d.hatena.ne.jp/hidde/20080930/1222776171
http://d.hatena.ne.jp/hirono_hideki/20090816/1250365295
http://blog.kaihatsubu.com/archives/001438.html


■覚えておかなくてはならないコマンド
\c 対話的に新しいディレクトリをプロジェクトに追加する。
\C \cと同じだが、指定したディレクトリ以下のすべてのディレクトリを再帰的にプロジェクトに追加する。
\r 現在カーソルがあるディレクトリの中身を再度読み込みなおす。プロジェクトの設定を変更したときやファイルを追加したときなどに実行する。
\R \rと同じだが、指定したディレクトリ以下のすべてのディレクトリを再帰的に読み込みなおす。
\w 現在開いているファイルを閉じる
\W すべての開いているファイルを閉じる
Space プロジェクトウィンドウを最大化する。もう一度押すといつものサイズに戻る。
以上、これだけ!


■使うときのコツ
\Cや\Rなどを使って新しいファイルをプロジェクトに取り込んだら、プロジェクト自体を:wして保存するのを忘れないようにする。

Python Hackathon #1に参加してきました

http://atnd.org/events/649

Python Hackathon #1に参加してきました。主催はいつものごとくvの人。しかしながら幸いにして雨は降りませんでした。


■以下、適当にダイジェスト
会場がいつもの四ッ谷健保会館ではなく、大久保の健保会館だったため場所を間違える人が続出。
今回のハンズオンは、
  • id:tokibitoのパーフェクトdjango教室
  • Google App Engineハンズオン Kayを使ってみよう
  • Pythonソースコード読書会
の3本立てだったのですが、Pythonソースコード読書会が参加者0人のためあえなく中止><

私はというと社内ブログの投稿順序を記録して利マインドメール投げたりするためのアプリをひたすら書いてました。が、前日夜遅くまで東方をプレイしていたせいで半分意識がもうろう・・・
気がついたらもう18時になって終わっていた感じです。PyPyとかwxPythonとかPyQtとかjQueryのカスタムイベントの話とかをしていたそうなのですが、意識が無くほとんど聞けずちょっと後悔。しかし後悔先に立たず・・・

18時半から毎度おなじみ@moriyoshiのすべらない話。

(音小さくてすみません、iPhone 3GSの限界)
相変わらず面白いなぁ。しかし本人曰く「納得いかない、やり直したい」とのこと。凄いw


■懇親会
普段は懇親会お断りしているのですが、今回は禁酒の上で懇親会に参加してみました。
http://www.yamachan.co.jp/index.html
場所は大久保のこの店。この手羽がまた美味しい!普段は飲み会など参加しても3分の1人前も食べない私ですが、今回は思わず一人前完食。話も盛り上がってなかなか充実したひとときでした。


■はんせい
  • 前日はちゃんと寝ておこう
  • ソースコード書いてばっかりないでもっと話そう
逆に良かった点としては、ちゃんと必要なライブラリとか実行環境とかを用意してきたためスムーズに作業に入れたところと、懇親会に参加したところかな。
当日のTwitterで見られたつぶやきのうちに、「話しかけるきっかけがないまま終わってしまった」というものがありましたが、納得。この手の勉強会は勉強会の最中よりも懇親会の方が話が弾んで名刺の交換もはかどるので、懇親会には出た方がお得ですね。勉強会の最中は相手もコード書いてたりでなかなか話しかけづらい雰囲気なんですよねー。その点、id:mopemopeさんとかは気さくに参加者に話しかけてくださってて凄いと思います。一見しゃべってるばかりで何もしてなさそうなのですが、話しかけてこの勉強会の緩い雰囲気を保ってくださっているんだなぁと感心。自分ももっともっと話していきたいです><

2009年8月10日月曜日

cocos2dのドキュメントをdoxygenで生成してみました




cocos2dの公式ブログに、ドキュメントをdoxygenでビルドしてXcodeのドキュメントビューワで見られるようにする方法が記載されていたので試してみました。
http://www.cocos2d-iphone.org/archives/358

■前準備
  • cocos2d for iPhone 0.8をダウンロードしておく。
  • doxygenをインストールする。どこからでもかまいませんが、私はMacPortsを使ってインストールしました。

■それでは早速doxygenでビルドしてみましょう
ダウンロードしてきたcocos2d for iPhoneの、cocos2d-iphone.xcodeprojをXcode開きます。開いたらターゲットからcocos2d-documentationを選択し「情報を見る」をクリックします。「ビルド」タブの中を調べると、上記の画像のようにDOXYGEN_PATHという項目がありますので、そこを自分の環境に合ったdoxygenへのパスに変更します。私の場合は/opt/local/bin/doxygenです。あとはこのcocosed-documentationをビルドするだけです。全部自動的にやってくれます。


■おまけに学んだこと
作成されたドキュメントは以下のパスに配置されるみたいです。
~/Library/Developer/Shared/Documentation/DocSets/
つまり、ここにDocSet形式のファイルを置けば自由にドキュメントをXcodeに追加することができるってわけですね。自分のプロジェクト用のドキュメントをdoxygenで生成したり出来そうです。

何でもいいから作ってみようと思って作った結果がこれだよ!




製作期間二日。この程度の物に二日とか許されるのは小学生までよねー・・・orz
一応自機も動くしあたり判定もあるしゲームオーバーにもなるよ!すごいよcocos2d!



以下、全然関係ないけど設計のお話です。

(2009/08/30追記) 以下の内容に従って作ってみた結果がこちら:http://akisute.com/2009/08/blog-post_29.html
見ての通り大失敗してます。やっぱり継承して作った方がうまくいくっぽいです・・・


Javaの継承システムでは、サブクラスはスーパークラスのコンストラクタを引き継がない。また、暗黙的にスーパークラスのデフォルトコンストラクタを最初に呼びだすことになっている。だから、これはコンパイルが通らないし、
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        // デフォルトコンストラクタがないし、明示的にSuper(String)を呼んでいない
        this.age = age;
    }
}
これもコンパイルが通らない。
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        super("child");
        this.age = age;
    }
}

public static void main(String[] args) {
    // ChildにはSuperのコンストラクタが継承されないのでこれはできない
    Child child = new Child("akisute");
}
ところがObjective-Cの継承システムでは、サブクラスがスーパークラスのイニシャライザをすべて受け継いでしまう。なぜならイニシャライザも何の変哲もないタダのメソッドだから。だから、こんなコードが書けてしまう。
@interface Parent : NSObject {
    NSString *name;
}
@end

@interface Child : Parent {
    int age;
}
@end

@implementation Parent
    - (id)initWithName:(NSString *)aName
    {
       if (self = [super init])
       {
           name = aName;
       }
       return self;
    }
@end

@implementation Child
    - (id)initWithAge:(int)anAge
    {
        // Parentにはinitが無いが、NSObjectにはあるので
       if (self = [super init])
       {
           age = anAge;
       }
       return self;
    }
@end

int main (int argc, char const *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // Childには無いinitWithNameが呼び出せてしまう
    Child *child = [[[Child alloc] initWithName:@"akisute"] autorelease];
    NSLog(@"@%", child);
    [pool release];
    return 0;
}
で、何が一体問題になるのかというと、この仕様のせい(おかげ?)で安易な継承が出来なくなっている。たとえば今回のcocos2dを使ったゲームでは、キャラは全部Spriteのサブクラスにしたかったんだけれども、そうするとSpriteクラスに最初から用意されているイニシャライザ(initWithFile:とかinitWithTexture:とか)が外に漏れてしまう。しかもこいつらを呼びだされると本来自分の作ったキャラクラスで呼びだしたかったイニシャライザが回避されて、任意の画像でへんちくりんなキャラを作られてしまう危険性がでてしまう。そこで、今回はこういうふうに継承ではなくてコンポジットを使ったコードにした。
@interface MSObject : NSObject {
    /** Sprite for this object. Can be changed to AtlasSprite for further performance. */
    Sprite *sprite;
    /** Hit box size */
    CGSize hitBoxSize;
}
後から考えたらこの方が正解だった。だって、こうしておけば外へ公開するAPIを変化させずに、中身のSpriteをよりパフォーマンスの良いAtlasSpriteに後からいくらでも差し替えられる!

2009年8月8日土曜日

cocos2d細かいところメモ

cocos2dを使っていて適当に気づいたところとかメモしてみます。

■ログ出力
CCLOGというマクロccMacros.hで定義されています。
#ifdef DEBUG
#define CCLOG(...) NSLog(__VA_ARGS__)
#else
#define CCLOG(...) do {} while (0)
#endif
注意点としてDEBUGがdefineされていないと使えません。


■ベクトル演算とか角度変換とか
CGPointを拡張してベクトル演算をするためのメソッドが追加されています。なかなか便利です。たとえばこんな感じ。
    // 二つのベクトルのdot(内積)とlength(ベクトルの長さ)を計算してcosθを求める
float dot = ccpDot(lastAccerelometerVector, convertedVector);
float a = ccpLength(lastAccerelometerVector);
float b = ccpLength(convertedVector);
float cosTheta = dot / (a*b); // 注意:a*bが0だとゼロ除算で死にます。真似しないでね><
// 角度→ラジアン変換用のマクロ。逆ももちろんあります
float threshold = cos(CC_DEGREES_TO_RADIANS(120.0));
Chipmunkにも同様のメソッドがあります。お好きな方をご利用いただけますが、個人的には全部cocos2dでやる方が好きです。唯一の問題は、こんなメソッドを使っているとベクトル内積の計算式を忘れます。


■音を出力する方法
最初からCocoa Touchで用意されているAudio Queue Service, OpenAL, AVAudioPlayer, Audio Unitに加えて、さらにcocos2dについてくる音再生用ライブラリとして
  • CocosDenshionとSimpleAudioEngine
  • sound-engine
これだけたくさんの中から選択できます。今回は一番簡単なSimpleAudioEngineという奴を使ってみました。
        // prefetch sound resources
SimpleAudioEngine *audioEngine = [SimpleAudioEngine sharedEngine];
[audioEngine preloadEffect:@"bell.aif"];
[audioEngine preloadEffect:@"gong.aif"];
[audioEngine playEffect:@"gong.aif"];
たったのこれだけです。playEffectでサウンドエフェクトが、playBGMでBGMが再生できます。同時再生数とか使用可能なファイルタイプとか細かいところは不明ですが、そこそこの量が同時に出せるみたいです。
注意点としてSimpleAudioEngineはCocosDenshionを使用しています。CocosDenshionのライセンスはcocos2dと独立しており、こちらは年間2500ドル以上の利益が出ている場合には500ドルのライセンス料を徴収するとかなんとかそういう条項があります。(http://www.cocos2d-iphone.org/wiki/doku.php/cocosdenshion:licenseを参照)


今はこのcocos2dの上にどのような構造でアプリを組んでやろうかとか考えてます。・・・あー、こんなこと考えてるからいつまで経っても本題のアプリが完成しないんだー ><

2009年8月7日金曜日

cocos2d for iPhone Project Template v0.8 真似して作ってみました



cocos2d導入キット(http://d.hatena.ne.jp/Seasons/20090511/1241990196)としてid:Seasonsさんが公開されているキットを元に、私もcocos2d用のプロジェクトテンプレートを作ってみることにしました。id:Seasonsさん、すばらしいキットをありがとうございます!

今回の作業にあたり参考にしたページはこちら。
http://d.hatena.ne.jp/griffin-stewie/20090315/p1

作成したテンプレートはgitに公開いたしましたので、よろしければ使ってみてください。
http://github.com/akisute/cocos2d-xcode-template/tree/master


■インストール方法
downloadタブから0.8をダウンロードしてきて、解凍したファイルを以下のパスに配置してください。
~/Library/Application Support/Developer/Shared/Xcode/Project Templates
配置後、.gitと.gitignoreファイルを削除してください。削除しないと後からテンプレを使って新しいプロジェクトを作ったときに、これらのファイルがコピーされてしまいgitを使おうとしたときに問題が発生する可能性があります。


■テンプレートの内容
id:Seasonsさんが公開されている0.8beta用のテンプレートを元に、一部自分の気になった箇所を自分好みに修正しました。また、使用するcocos2dのバージョンを0.8betaから0.8リリース版に変更しました。一応ビルドして画面が出るところまでは確認しています。ただし、全機能をテストしたわけではないので、一部不具合があるかもしれません!
また注意点として、簡素化のため元のcocos2dや外部ライブラリに付随していたREADMEやドキュメント、LICENSEファイルなどをすべて削除してしまっています。おそらくは大丈夫だと思いますが、ライセンス的に問題が発生するかもしれません。大変申し訳ありませんが、このテンプレートを利用した際に生じる一切の不具合について当方では責任を負いきれませんのでご了承ください。


■id:Seasonsさんのテンプレートから変更したところ一覧
  1. 使用するcocos2dのバージョンを0.8リリース版にした。
  2. プロジェクトのグループ構成およびディレクトリ構成を変更した。この変更により、本来システムが利用する/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application以下ではなく、ユーザーのライブラリである~/Library/Application Support/Developer/Shared/Xcode/Project Templates以下にテンプレートを配置しても動作するようになった。
  3. MenuSceneとGameSceneが最初のテンプレとして用意されていたのを統廃合し、MainSceneひとつだけにした。
  4. ビルドの設定を必要に応じて変更し、またGCC_PREPROCESSOR_DEFINITIONSにDEBUGを追加した。これにより最初からデバッグビルド時にCCLOGマクロが正しく機能する。
  5. ソースコードのインデントがまちまちになっていたのを、すべてApple式のインデントに修正。
  6. ソースコード中から日本語コメントをすべて除去し英語コメントに置換。
  7. 一部使用されていたdepreciatedメソッドを除去。[[Director sharedDirector ] setDeviceOrientation:CCDeviceOrientationLandscapeRight];を使うようにした。


■思わぬ収穫
この作業によってXcodeの仕組みとテンプレートの作り方、ビルドの仕方などにかなり詳しくなることができました。たとえば・・・
  • Xcodeのテンプレートファイルは単なる普通のXcodeプロジェクトと何ら変わらない。そのままビルドして実行することもできる。
    唯一異なる点が、xcodeprojバンドルの中に、TemplateIcon.icnsとTemplateInfo.plistというファイルが存在し、これらを変更することでテンプレートとしての設定を変更することが出来る点。
  • libhogehoge.a(静的ライブラリ?)の作り方。ターゲットに新規ターゲットを追加して、必要なソースをぽいぽい投げ込むだけ。antでbuild.xmlを書くより簡単。
  • ___PROJECTNAMEASIDENTIFIER___.pchの配置パスを変更したときは、ターゲットのビルド設定のPrefix Headerの値を書き換える必要がある
  • 同様に、___PROJECTNAMEASIDENTIFIER___-Info.plistのパスを変更したときも、ターゲットのビルド設定のInfo.plistファイルの値を書き換える必要がある
  • main.mは何処に置いておいても大丈夫らしい

ということで、Xcodeプロジェクトのテンプレートを作成するのは凄く勉強になります。皆さんも一度自分好みのテンプレートを作成してみてはいかがでしょうか?

2009年8月4日火曜日

cocos2d for iPhoneをためしてみました



YourTurnが一段落したので、今度は前々から一度やってみたかったゲームにチャレンジしてみようと思い、cocos2d for iPhone(http://code.google.com/p/cocos2d-iphone/)を使い始めました。cocos2d for iPhoneとは、iPhone上で簡単に2Dのゲームを作成するためのフレームワークです。FPS管理、アニメーション、パーティクル、シーンの管理およびアニメーション付きの切り替え、メニュー、タッチおよび加速度イベントの管理など、ゲームに必要な要素をほぼすべて網羅してくれています。さらにオプションとしてBGMやサウンドエフェクトの管理と、2D物理エンジンも用意されており、まさに至れり尽くせりといった感じです。

まずは手始めにチュートリアルをやってから、Tic Tac Toe(いわゆるタダのマルバツゲーム)を作ってみました。
ソース全体 http://github.com/akisute/Cocos2DTest/tree/master
ヘッダ部分のみ http://gist.github.com/160244
実装部分のみ http://gist.github.com/160246

この程度の内容のソースでパーティクルも作れるし音も鳴らせるしで、本当に簡単です。ここまで簡単だとRPGツクールより簡単かもしれませんw
今までゲームプログラミングの経験が全然無い人のとっかかり用としては最適ではないでしょうか?

2009年7月25日土曜日

Bloggerにretweetボタンを付ける方法

IDEA*IDEAさんで紹介されていた、こちらの記事(http://www.ideaxidea.com/archives/2009/07/retweet_js.html)を参考にして、私のBlogにも早速Retweetボタンを設置してみました。Bloggerでこのボタンを設置するにはちょっとだけ工夫が必要でしたので、私が行った設置方法を簡単にご紹介いたします。


■1.スクリプトを読み込ませる
まずは以下のスクリプトタグを、Bloggerテンプレートの<head>要素内に設置します。
<script src='http://ejohn.org/files/retweet.js'/>

■2.ボタンを配置する
スクリプトタグを置いたら、後はボタンを好きな場所に置くだけです。以下のソースを任意の場所にコピペしてあげてください。
<a class='retweet' expr:href='data:post.url' expr:title='data:title + ": "+ data:post.title' />
たったのこれだけでretweetボタンを配置することができます。いやー便利。


■トラブルシューティング
(1) もし上記のソースをコピペしても動かない!という場合には・・・
コピペする際にソースコード中の
"

& quot;
要するにHTMLの文字実体参照に差し替えてください。
※残念ながら、Bloggerの本文中にこの記号が含められないのです・・・

(2) もしボタンを配置する場所が分からない場合には・・・
コメント欄でご指摘いただいたのですが、Bloggerのテンプレートは長くて複雑で、初めての人には何処に何を置けばいいのか分かりづらいです。ということで、各投稿のフッター欄(post-footer-line)にRetweetボタンを配置するサンプルソースを書いてみました。
    <div class='post-footer'>
    <div class='post-footer-line post-footer-line-1'>
      <span class='post-ほげほげ'>
      <!-- ここになにやら難しい記述がたくさん -->
      </span>
      <!-- post-footer-line post-footer-line-1の一番最後に追加 -->
      <a class='retweet' expr:href='data:post.url' expr:title='data:title + &quot;: &quot;+ data:post.title' />
      
      </div> <!-- post-footer-line post-footer-line-1 ここまで -->

      <div class='post-footer-line post-footer-line-2'>
      <!-- ここになにやら難しい記述がたくさん -->
      </div>

      <div class='post-footer-line post-footer-line-3'>
      <!-- ここになにやら難しい記述がたくさん -->
      </div>
    </div>
  </div>
</b:includable>

■ちょっとだけ補足説明
http://ejohn.org/blog/retweet/に記載されているコードをそのまま何も考えずに貼り付けると、Retweet先が現在ロケーションバーに表示されている先になってしまいます。
このコードを貼り付けて自分のBlogのトップページなどからRetweetボタンを押してみると、すぐに変だと気づくはずです
<a class='retweet self' href='' />
そこでBloggerのテンプレートXMLの記法を利用して、現在の記事のタイトルとURLを正しく指定してやります。具体的にはexpr:hrefとかdata:post.urlとかです。どうやら、expr:をhtmlの任意の属性に付けてやると、data:post.urlとかdata:titleとかが利用可能になるっぽいです。expr:を付けた属性の中で任意の文字列リテラルを利用したいときは& quot;で文字列を囲み、+で文字列を結合してやることができるみたいです。

2009年7月19日日曜日

YourTurn 1.1 サブミットしました

YourTurn 1.1をApp Storeにサブミットしました。現在レビュー待ちです。
http://wiki.github.com/akisute/YourTurn


■そもそもこのアプリ何?
学会タイマーアプリといえばわかりやすいと思います。要するに参加者ごとに時間を決めて割り振り、時間が来たらタイマー音がなる、それだけのアプリです。





2分程度のビデオデモを作ってみましたので、よろしければご覧ください(無音ですけど・・・)。


順調にレビューが進めば2週間後の8月上旬にはストアに並ぶと思います。無料、というかソースも全部公開してます。気が向いたらプレゼンしたりするときにでも使ってやってください。

iPhone OS 3.0のTableViewでscrollEnabledをNOにするとtableView:didSelectRowAtIndexPath:が機能しない

http://forums.macrumors.com/showthread.php?t=470266

OS2.2のころはscrollEnabledをNOにしても正常にdidSelectRowAtIndexPathが動いていたので、これは正直結構困ります。仕方がないのでscrollEnabledを使うのを回避して、スクロールするように戻しました。OS 3.1では直っているとよいのですが。

iPhone付属のPhotos(写真)アプリのような、回転可能な全画面表示ビューを作る方法



Photos(写真)アプリに使われている、全画面ビューを真似して作ってみました。具体的には以下のような仕様になります。
  • ステータスバーの後ろも含め、ビューの内容が全画面(320x480)で表示される
  • 画面をタップするとステータスバーとナビゲーションバーが消える
  • もう一度タップすると再度表示される
  • iPhoneを傾けると画面が回転する
参考にしたページは以下のとおり。
http://life.ponpoko.tv/?p=533

また、サンプルソースも公開してます。こちらのソースの、- (void)fullScreenMode:(BOOL)mode animated:(BOOL)animatedがフルスクリーン表示を実現しているメソッドになります。


■注意
iPhone OS 3.0以上専用です。2.2以下では別の方法を取る必要があります。


■まずはStatus BarとNavigation Barを消す
http://life.ponpoko.tv/?p=533 こちらのページに書いてある内容を100%そのままパク参考にさせていただければ何も問題ありません。iPhone OS 2.2までは記載されている通りのworkaroundが必要になりますが、3.0以降ではバグが修正されているため、単にStatus BarとNavigation Barを消せばOKです。

ただし一点、[self.navigationController setNavigationBarHidden:YES animated:YES];を使うと、ナビゲーションバーが上方向に対してぴょこんと引っ込むようなアニメーションになります。ステータスバーはその場でフェードアウトして消えるので、なんだか不自然な感じに見えます。Photos.app(写真アプリ)など、iPhone標準のアプリと同様にナビゲーションバーをフェードアウトさせて消したい場合には、UIViewのアニメーション機能とalpha値を利用して以下のようにします。
    if (animated)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
}
self.navigationController.navigationBar.alpha = mode ? 0 : 1;
if (animated)
{
[UIView commitAnimations];
}
Three20プロジェクトのソースから拝借してきました><


■Viewを全画面配置する(Status BarとNavigation Barのあった場所にも描画させたい)
さんざん悩んだあげく、実はUIViewControllerにそのためのプロパティがあったと知り、ものすごく簡単に解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
// このUIViewController.wantsFullScreenLayoutをYESにするとフルスクリーン表示になる
self.wantsFullScreenLayout = YES;
}
ついでにステータスバーとナビゲーションバーを透明にして裏が透過するようにしてみました。


■ビューの回転に対応したい
何も考えずにshouldAutorotateToInterfaceOrientationすればいいだろう常考
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
とおもったら思わぬ落とし穴が。確かに全画面表示にした状態でビューを回転させたり、逆にナビゲーションバーが出ている状態でビューを回転させたりする分にはこれだけで全く問題ないのですが、
  1. ナビゲーションバーを消す(全画面表示にする)
  2. iPhoneを傾けてビューを回転させる
  3. 再度ナビゲーションバーを表示する
という手順を踏むと、再度表示したときにナビゲーションバーが崩れる問題が発生。結局ナビゲーションバーを消したり再表示したりする際に、以下のようなworkaroundをするハメに。
    // Force set the frame of the navigation bar
CGRect frame = self.navigationController.navigationBar.frame;
frame.origin.y = 20.0;
self.navigationController.navigationBar.frame = frame;


■全画面表示をしている画面から前の画面に戻るとバグる問題
これで完成かと思えば、最後にもう一つ落とし穴が。
  1. iPhoneを横向きに傾けて、Landscape表示にする
  2. ナビゲーションバーを表示する
  3. そのまま横向きになっている状態で、ナビゲーションバーの戻るボタンを押して、前の画面に戻る
  4. 前の画面に戻る際に、wantsFullScreenLayoutをNOにして通常画面モードにし、ステータスバーとナビゲーションバーのスタイルもDefaultに戻す
という風な操作を行うと、なぜかナビゲーションバーのスタイルだけがDefaultに戻らず、表示も崩れるという問題が発生しました。たぶんOS 3.0のバグだと思います。
いろいろ試してみた結果、全画面表示をしている画面ではなく、前の画面のviewWillAppearのタイミングで以下のコードを呼びだせば問題が解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
self.navigationController.navigationBar.barStyle = UIBarStyleDefault;
}

CGGradientを用いてUITableViewCellを描画し、テーブルをカッコよく見せる方法

デフォルトのUITableViewCellの背景が白くてのっぺりでいまいち味気ないと思い、背景にグラデーションを付けてかっこよく見せる方法を調べてみました。単純に別途用意した背景画像をbackgroundViewに表示してもよいのですが、Cocoa Touchの2Dグラフィックスライブラリにはグラデーションを描画するためのCGGradientというクラスが最初から用意されています。さっそく私もパクってインスパイアされてやってみました。

参考にしたページはこちら。
http://developer.apple.com/documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html#//apple_ref/doc/uid/TP30001066-CH207-TPXREF101


■どこで描画するか
  • UITableViewCellのdrawRectで直接描画。
    少しでも高速に描画したい場合にはこの方法
  • 新規にUIViewを継承した背景用Viewを作成しセルのbackgroundViewに設定。そのViewのdrawRectで描画
    複数のUITableViewCellで同じ背景を適用したいときはこの方法が便利

■まずは実際に描画してみる
drawRectの中でCGContextを作成して、続いてCGGradientを生成。CGGradientを作るためにはCGColorSpaceとか色を指定する配列とかが必要になるのでそれらも生成。最後に生成したCGGradientオブジェクトを描画するという流れになります。ということでソースを見てみましょう。
- (void)drawRect:(CGRect)rect
{
// CGContextを用意する
CGContextRef context = UIGraphicsGetCurrentContext();

// CGGradientを生成する
// 生成するためにCGColorSpaceと色データの配列が必要になるので
// 適当に用意する
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
0.79, 0.79, 0.79, 1.0 }; // End color
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace, components,
locations, num_locations);

// 生成したCGGradientを描画する
// 始点と終点を指定してやると、その間に直線的なグラデーションが描画される。
// (横幅は無限大)
CGPoint startPoint = CGPointMake(self.frame.size.width/2, 0.0);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
}
このコードを実行すると・・・


はい!もうグラデーションができました。嘘みたいに簡単です。

2010/05/24追記:注意点として、CGGradientとCGColorRefのオブジェクトは手動でリリースしないとメモリリークが発生します!


■UIColorを元にグラデーションを作る
先ほどの例では配列にRGBA要素を渡してグラデーションを作りましたが、UIColorが使えるともっとお手軽で、しかもRGBAだけではなくてHSBAで色が指定できて何かと便利です。ということで、次はUIColorからグラデーションを作ってみます。

UIColorから直接CGGradientを作ることは出来ないので、途中でCGColorとCFArrayRefを作り、それを元にCGGradientを生成してみます。コードはこちら。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *currentColor = [UIColor colorWithHue:currentBackgroundColorHSBA[0]
saturation:currentBackgroundColorHSBA[1]
brightness:currentBackgroundColorHSBA[2]
alpha:currentBackgroundColorHSBA[3]];
// gradient background
CGGradientRef grad;
CGColorSpaceRef colorSpace;

// UIColorからCGColorを取り出すのはとっても簡単
CGColorRef currentColorRef = [currentColor CGColor];
CGColorRef voidColorRef = [[UIColor colorWithHue:0.0
saturation:0.0
brightness:0.17
alpha:1.0] CGColor];
CGColorRef colorArray[2] = {voidColorRef, currentColorRef};
CGFloat locations[2] = { 0.0, 1.0 };

// CFArrayRefを作る。
// もっと簡単に作りたければ、NSArrayを作ってからCFArrayRefにキャストするだけでもよい。(未確認)
CFArrayRef colors = CFArrayCreate(kCFAllocatorDefault, (const void **)colorArray, 2, &kCFTypeArrayCallBacks);

colorSpace = CGColorSpaceCreateDeviceRGB();
// Gradientを生成する
grad = CGGradientCreateWithColors(colorSpace, colors, locations);

// 描画する
CGFloat progress = currentTime / allottedTime;
CGFloat height = self.frame.size.height + HEIGHT_GRADIENT;
CGPoint startPoint = CGPointMake(self.frame.size.width/2, progress * height - HEIGHT_GRADIENT);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, progress * height);
CGContextDrawLinearGradient(context,
grad,
startPoint,
endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(grad);
}
CGContextDrawLinearGradientの第4引数にkCGGradientDrawsBeforeStartLocationフラグとkCGGradientDrawsAfterEndLocationフラグを設定しています。これらのフラグを指定すると、startPoint以前、およびendPoint以降をグラデーションの開始色と終了色でべた塗りしてくれます。実行結果は以下の通り。


こんな感じで、画面中央から下が全部べた塗りになっているのが分かると思います。

他にも、CGGradientを作る際にlocationsを増やせば2色だけではなくて多色のグラデーションを作成することが出来たりします。このあたり、Appleが公開しているドキュメントだけでも相当詳しく紹介されているので、そちらを見ればほぼ間違いないかと。


■サンプルソース
今回のサンプルはgithubですべてソースを公開して居ますので、より詳しく学びたい方はそちらも併せてご参照ください。
http://github.com/akisute/YourTurn/tree/master
これとかこれを見るとよいかと思います。

2009年7月12日日曜日

vim javascript indent plugin syntax

javascript用のvimプラグインがたくさんあって探してもこれといったまとめがなかったので探した範囲でまとめました。タイトルが釣りっぽいです。ごめんなさい。


■syntax
JavaScript syntax : Better JavaScrirpt syntax support
http://www.vim.org/scripts/script.php?script_id=1491

悪くはないです。ただし中身を見た感じFirefox + Dojoで使うことを想定されているようで、jQueryとかでprototype.jsでハイライトして欲しいオブジェクトがなかったりします。気に入らなければ適当に改造するのがよいと思います。それに、所詮syntaxですから無くてもあんまり困りません。


■indent
よさげなのが2つあります。まず一つ目、Ryan Fabellaさん作。スタンドアロンで動くもの。
OOP javascript indentation : This indentation script for OOP javascript (especially for EXTJS)
http://www.vim.org/scripts/script.php?script_id=1936
especially for EXTJSとか書かれてますがjQueryでも全く問題ないです。快適。

実際にインデントしてみた結果はこんな感じです。




//でコメントアウトすると完全にレイアウトが壊れてしまいます。/**/を使えば問題ないようです。


二つ目、Tye Zdrojewskiさん作。別にプラグインが必要になるもの。
http://www.vim.org/scripts/script.php?script_id=1840
会社のマシンにはこちらを入れています。実際にインデントしてみた結果はこちら。





こちらは//だと問題ありませんが、/**/だと崩れてしまいます。あと、$(function(){hogehoge...});を綺麗にインデントできていません。


どちらも一長一短なので、どちらを使うかは好みの問題ですね。


■plugin
IndentAnything : Write indentations or enhance existing indentations without writing code
http://www.vim.org/scripts/script.php?script_id=1839

Tye Zdrojewskiさんのjavascript indentを動かすために必要です。なんか勝手にIndentAnything_htmlなんていうindentが付属で付いてきますので、既にhtmlのインデントを持っている人は注意です(IndentAnything_htmlはおまけなので入れなくても大丈夫だと思います)。

EditControlとAccessoryViewの背景は透明



  • EditControlの背景はデフォルトで透明
  • AccessoryViewの背景もデフォルトで透明
  • UILabelの背景はデフォルトで白塗りつぶし(透明にはならない)

OS 2.2.1での実験結果なので、3.0では違うかも(たぶん同じ)

それだけです。お粗末様でした・・・

2009年7月7日火曜日

YourTurnをApp Storeにサブミットしてみた



2ヶ月ぐらい前からちょこちょこ作っていた物をレビューに出してみました。まぁ、どうなるかは分かりません。現在一週間経過、特に何も変化無し。今回の目的であるApp Store童貞を捨てることは見事達成できそうです><


公開されるのを気長に待ちながら、現在1.1アップデートを作成中。セルがかっこよくなりました。

2009年7月5日日曜日

iMacとiPhoneでBluetoothのヘッドセットを使ってみた

以前からずっと気になっていたBluetoothヘッドセットをこのたび購入したので、iPhone 3GやiMacとつないでみました。
使用感などをレポートしてみたいと思います。


■購入したヘッドセット
某所で紹介されていた、「Bluetoothステレオヘッドセット ELANVITAL EVSH-2882」という奴です。ネックバンド型でA2DPに対応していて通話ももちろん出来ちゃう、その上8000円未満となかなかお買い得。(ソニーとかモトローラのやつは1万6000円とかしますからね><)


■iMacで使う
まずは「環境設定」から「Bluetooth」を開いてヘッドセットとペアリングさせます。ペアリングさせると、「ヘッドセットを有効にする」と「ヘッドフォンを有効にする」の二つが選択できる様になります。

こんな感じ。

実はここに落とし穴があって、
  • ヘッドセットを有効にすると、HSPを用いてiMacとヘッドフォンが通信を行う。
    そのため音楽等を聞くとモノラルで電話並みのひどい音質になる。
  • 逆にヘッドフォンを有効にすると、A2DPを用いて通信が行われるため音質は最高になるが、
    ヘッドセットとして認識されない(要するにヘッドセット内蔵のマイクが使えない)
何この残念な状況orz

MacのBluetoothの扱いが悪いのか、このヘッドセット自体に問題があるのか、それともBluetooth2.1の規格がそもそもそんな使い方を想定していないのかは知りませんが、とにかく「Mac OS X 10.5.7上でELANVITAL EVSH-2882を使う場合、そのままではステレオ音質でヘッドセットとしてマイクを自由に使うことはできない」というのは間違いなさそうです。

それでもいろいろ試して見た結果、以下の手順を取ればステレオの音質でマイクが使えるようになりました。
  1. 「ヘッドフォンを有効」にする。
  2. 「環境設定」の「サウンド」を開き、出力と入力を以下のように設定する。

    出力は、「Bluetooth ヘッドフォン」を選択。


    入力は、「Bluetooth ヘッドセット」を選択。
この設定を行った状態でSkypeを開き、オーディオデバイスを適当に設定して友人とSkypeで通話してみましたが、無事ステレオ音質でマイクが使えました。しかしこの方法は何かしらの無理をしているようで、突然音質がモノラルになったり、突然マイクが使えなくなったりします。その上バックグラウンドで動かしていたiTunesの音が全く聞こえませんでした。ということで、おすすめできません。


■iPhoneで使う
設定方法とかはAppBankさんのこちらの記事が詳しいのでそちらをご参照をば。
http://www.appbank.net/2009/06/19/iphone-news/32183.php
http://www.appbank.net/2009/06/21/iphone-application/32492.php

使ってみた感想ですが、A2DPのおかげで音質はかなり良いです。私はオーディオマニアではないので参考にはならないかもしれませんが、有線のヘッドフォンと全く違いが分かりません。普段から1万円以上のイヤフォンやヘッドフォンを使われるような方でなければ問題ないと思います。

着信があったときの移行もスムーズです。また、着信を受けてからBluetoothヘッドセットの電源を入れて、オーディオデバイスをそちらにに切り替えても問題なく通話が出来ました(ただし友人曰く、ちょっと音が遠いとのことなので、ひょっとしたらiPhone本体のマイクで音を拾っていたのかも)

Macでは音声出力先・入力先ごとに音量を保持してくれていたのですが、iPhoneではどうやらそのような機能がないようです(すべての音量が共通)。そのため、Bluetoothヘッドセットで小さいと思った音がiPhone本体のスピーカーでは大音量と言うことがあります。ちょっと不便。

最大の問題が、「一時停止」「次の曲」「前の曲」ボタンがすべて利用できないことです。私は結構頻繁に曲を次に送ったり一時停止したりする使い方をするので、これらのボタンが使えないのが苦痛になっています。
ひょっとしたら使える機種もあるかもしれませんが、少なくとも今回私が購入したものではダメでした。気になる方はご自身で確認されることをお勧めします。

電池の消耗については、有線よりは確実に減ります。どれぐらいがっつり減るかは未確認ですが、1時間程度付けっぱなしにしていると10%ほど減っていました。(別の要因もあるので一概には決められませんが・・・)

なお、音声コントロールはBluetoothヘッドセットが使用できませんでした。ヘッドセットをペアリングさせて使える状態にしていても、本体のマイクとスピーカーが使用されてしまいました。

結論としては、聞くだけ・通話するだけ専門としてはほとんど申し分ありません。ケーブルが邪魔になるジムでの運動時などでは最大限に威力を発揮すると思います。逆に普段使いでは電池の消耗・iPodコントロール不可などのデメリットが目立つかと思います。


■iMacとiPhoneで併用
ペアリング自体は全く何の問題もありません。iPhoneとiMacの両方に同じヘッドセットをペアリングさせることが出来ました。
しかし当然ですが同時に使うことは出来ません。ヘッドセットの電源を入れた後、「先に認識を完了した」デバイスが独占的にヘッドセットを使います。認識が遅れた方はエラーになりヘッドセットを使うことが出来ません。
iPhoneとiMacの両方のBluetoothがオンになっていると、どちらが先に認識するか安定しないため、
  • iPhoneで使うときは、iMac側のBluetooth自体をオフにする
  • iMacで使うときは、逆にiPhone側のBluetoothをオフにする
という面倒な作業が必要になります。結局、私はiPhone専用としてヘッドセットを使うことにしました。