Objective-C で AUTOLOAD (あるいは method_missing )

forwardInvocation とかを使えば出来るみたい。

#import <Foundation/Foundation.h>

@interface Foo : NSObject;
-(void)call:(NSString*)sel;
@end

@implementation Foo

-(void)call:(NSString*)sel {
    NSLog(@"call: %@", sel);
}

-(void)forwardInvocation:(NSInvocation *)invocation {
    [self call:NSStringFromSelector([invocation selector])];
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature* sig = [super methodSignatureForSelector:sel];
    if (sig) return sig;

    return [[self class] instanceMethodSignatureForSelector:@selector(call:)];
}

@end

int main() {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    Foo* foo = [[Foo alloc] init];

    [foo bar];
    [foo buzz];

    [foo release];

    [pool drain];
    return 0;
}

// gcc -framework Foundation foo.m

詳細は Objective-C Runtime Programming Guide にある。

by typester / at 2011-12-05T20:32:00 / iphone · objc / Comment

libuv を iOS 対応した件

だいぶ前から iPhone アプリ作成でネットワーク系の機能を作るときには libev を愛用してるのだが、今日 node をいじっていたら libuv がなかなかよさそうに感じた。

libuv は libev が Windows で動かないからっていう理由で始まったプロジェクトだとおもうけど、Linux などの環境においても libev をラップしつつ、より便利な機能が追加されていていわば C でネットワークプログラミングするためのフレームワークといえるくらいになってる、みたい。(まだ詳しく見たわけじゃないけど)

libev は基本的に io 監視と timer のみで、socket の生成などは基本的に自分で syscall 呼んでつくる必要があるけど、libuv は uv_tcp_* とか uv_udp_* といったAPI郡をもってて便利そう。 あと、非同期 DNS ルックアップがサポートされてるのはうれしい。これ、 libev になくて不便だった。

そういうとこまでラップしないと同じコードを Windows でも動かすってのは無理だからそうなったんだとおもうけど、結果として大変便利なライブラリになっているのではないか。

あと、テストケースが充実しているので、(たぶん)すべての機能のサンプルコードがテストを見ればOKっていうのもうれしい。

っていうわけで iOS 対応のパッチを書きました。

#243: Added experimental iOS support by typester for joyent/libuv - Pull Request - GitHub

iOS で動かない理由はハードウェア時間をnano秒で取得してる関数が iOS にはない CoreServices.framework に依存しちゃってるからってことだけなので、それを使わないように修正をした。

現状のパッチは iOS 用にビルドするときのみ CoreServices 非依存のコードを使うようにしているけど、これに依存してることで uv.a を組み込むだけじゃなくて、 CoreServices.framework に別途リンクする必要があってめんどいからパフォーマンスや精度が問題なければ非依存のコードの方に統一してしまった方がいいと思う。っていうようなことは pull req に返信が来たら伝えようとは思っている。

とりあえず、これで使えるようになったから。さっそく今作ってるアプリに組み込んでにようと思う。

by typester / at 2011-11-10T20:51:00 / libuv · iphone / Comment

node.js を iPhone アプリから動かす

結論から言うと、 jailbreak してないと動きません。詳細はこの辺:

Issue 1312 - v8 - It's time to get iOS supported! - V8 JavaScript Engine - Google Project Hosting

というわけでターゲットがデベロッパー用に絞られてしまいますがやってみましょう!

Node ダウンロード

今回はリリースされたばかりの 0.6.0 を使いました。

http://nodejs.org/#download

からダウンロードして展開しましょう

libuv にパッチを当てる

libev はなにもしないでもそのまま iOS で動きますが、 libuv はそうではないようです。

$ cd deps/uv
$ wget --no-check-certificate https://raw.github.com/gist/1354552/cfb4e9a544185bdbda1a8374aaf1cd5cc812c070/libuv-ios.patch
$ patch -p1 < libuv-ios.patch

なお、このパッチは本家に pull req 送っておきました。取り込まれると良いですね。

V8 にパッチを当てる

V8 自体は ARM に対応しているようですが、iOS SDK でクロスコンパイルしようとすると

ARM EABI support is required.

とか言われてしまいます。iPhone の ARM が EABI をサポートしてないためでしょうか。

ただ、最初にリンクした Goole Code の issue 内で iOS 対応のパッチを投稿している人がいて、そのパッチを当てることでビルド可能になる模様。

$ cd deps/v8/src
$ patch -p0 < ~/Downloads/v8-ios.patch

Node にパッチを当てる

プロセスリストにでるプロセス名を設定する機能が Carbon をつかっているので関数殺しちゃいます。iOS でプロセス名かえられても誰得ですよね。具体的には

process.title = "hoge";

が動かなくなります。

$ wget --no-check-certificate https://raw.github.com/gist/1354570/0c28584c07f0410c5e5608d4e2e9ea68e6d5dbc4/node-ios.patch
$ patch -p1 < node-ios.patch

ビルド

まずもろもろ環境変数をセットし:

export CC=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc
export CFLAGS="-arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -I$HOME/dev/iphone/lib/openssl/include"
export CXX=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++
export CXXFLAGS=$CFLAGS
export CPP=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/cpp
export AR=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ar
export LINKFLAGS="-arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -L$HOME/dev/iphone/lib/openssl/lib"

ビルド:

./tools/waf-light configure build --product-type=cstaticlib --dest-cpu=arm --without-snapshot

ひっそり、前もってビルド済みの openssl を参照していますが、ない場合は --without-ssl を追加すればいけるはずです。

成功すると

./out/Release/deps/uv/uv.a
./out/Release/libnode.a
./out/Release/libv8.a
./out/Release/libv8preparser.a

といった静的ライブラリが出来てると思うんで、これらをアプリに埋め込んであげればいいわけですね。

armv6, armv7, i386 用それぞれビルドし、lipo でユニバーサルバイナリに仕上げるのが iOS 用静的ライブラリ作成のセオリーですが、ここではめんどいので省略します。

アプリ

だいぶ適当ですが、サンプル作ってみました。

https://github.com/typester/ios-node-test

lib ディレクトリにさっきビルドした .a ファイルを突っ込んであげる必要があります。 また node.h の参照元を nave でインストールした node のパスを設定してあるので、違う環境の人は Header Search Path を環境に合わせて書き換える必要もあります。

20111110195319

こんな感じの画面が出るんで、適当にコード書いて Run おせば動かすことが出来ます。

注意

コードみてもらえばわかりますがだいぶ適当につくっておりまして、node の標準出力などをうけとっておりません。また、二回目実行しようとするとアプリが落ちますw

直していただける方お待ちしております!

by typester / at 2011-11-10T19:12:00 / node · iphone / Comment

iPhone4S とか iOS5 とか Xcode 4.2 とか

iPhone4S は予約しようと思ってたけど、予約開始日がちょうど F1 日本 GP とかぶっていたので初日予約はあきらめ、暇なときに予約しに行こうと思っていて今に至る…。

iOS5 は開発者用のものを前から使っていたけど、通知で邪魔されないようになったのがすごい大きい。 いままではカーナビとして使っている最中に Push 通知が来てしまうと通知を消すまでカーナビとしての役割をはたさないようになってしまっていたからね。

で、こういうApple製品リリースの中で一番ライフチェインジングだったのが Xcode 4.2! ARC (Automatic Reference Counting) と Storyboard が便利すぎて iOS アプリの開発速度いままでの何倍にもなります。ただ Storyboard つかっちゃうと iOS5 以降限定になっちゃうってのが…。

ARC も zeroing weak 変数(破棄されたらnilになってるって保証されてるweak変数)使おうとすると iOS5 限定になっちゃうみたいだし。 zeroing ってなんて訳せば良いんだろ。

そんなこんなで、次のアプリから iOS5 のみで良いかなとか考えてる最中。

by typester / at 2011-10-27T18:03:00 / iphone / Comment

最近の活動その1 Reengo

気づいたら3ヶ月も更新途絶えてましたが、その間ずっとiPhoneアプリを開発してました。

今年一番時間をかけ、いま現在もつきっきりで開発しているのが Reengo というアプリ。

Reengo - 番号なしで電話できるアプリ - KAYAC Inc.

いわゆる VoIP アプリケーションですが、Facebookアカウント連動で友達がアプリさえインストールしていればすぐに通話できるというのが特徴です。

この辺の投稿なんかはこれの伏線だったわけです。

クライアント実装は通信周りのロジックは C で書いていて、この部分は近日リリース予定の Android 版と共有して使いまわしてます。 サーバー実装は Perl と Node を適材適所使い分けております。Node の使用用途としてはいままで Perl の AnyEvent を使ってやっていた部分を Node に置き換えたというイメージで、個人的にはこれはなかなかいい選択肢なんじゃないかなーとおもってます。 技術的なところの詳細はまたどこかで別途書きたいと思ってます。

まだまだ課題があってそれを徐々につぶしているという状態ですが、ぜひ使ってみてください!

by typester / at 2011-06-16T11:30:00 / life · iphone / Comment

iOS の socket(2) は 0 を返すことがある

iOS 4.2.1。

-1 チェックしかしていなくて、まさかここだとは思わずごちゃごちゃ迷走してしまった。。 あり得ないと思うことは表明(assert)しとくっていう基本をちゃんとしたい。

0 のときは無視して単純にもう一度 socket(2) を呼んであげるとちゃんと返ってくると言う謎。 その実装で fd も増えていかないのでとりあえずしのぐ。

by typester / at 2011-03-08T07:35:00 / iphone / Comment

iPhoneアプリケーション多言語化をpoファイルを使ってやる方法

iPhoneアプリケーションを他言語対応したいとしたら、Apple側がすでに用意してくれている方法を使うのが自然かと思う。 僕も最初はその方法を試した。ドキュメント的には以下がエントリポイントだろうか:

iOS Reference Library - Internationalization Programming Topics

しかしこの方法にはいろいろな問題があり、大きなアプリケーションを作ろうとするとストレスがかかってくるので現在は使用していない。(小さなプロジェクトでは使うこともある)

その問題とは以下のようなものだ:

  • 言語ファイル(Localizable.strings)が独自形式なため、既存の翻訳ツールを使うことが出来ない
  • 一つ目と少しかぶるが翻訳抜けや言語一覧などをチェックする機能がない
  • ibtool のマイグレーション機能がくそで、バージョンアップのたびにリソースファイル内の文字列を頑張って置換する作業が必要になる

など。これらを解決するために現在は以下のような手法でアプリの多言語化を行っている:

  • 言語ファイルには一般的なファイルである GNU gettext のカタログファイル形式(.po)を使う
  • ソースファイルから NSLocalizedString 系の呼び出しを抽出して .po ファイルの雛型を作成してくれるスクリプト(xgettext.pl)を作成
  • .po ファイルを Localizable.string に変換するスクリプト(po2string.pl)を作成
  • .nib ファイル内の多言語化が必要な文字情報はすべてインタフェースビルダーでは設定せず .m .mm ファイルからセット(上のツール群をそのまま使うため)

と言う感じでやっている。その二つのスクリプトはこんな感じ。

https://gist.github.com/854276

Locale::Maketext::Extract に依存している。

$ ./xgettext.pl -o resources/ja.po src/*.m

みたいな感じでまず言語カタログを作成。このファイルは Emacs の po-mode や Poedit などのツールで編集することができる。 個人的には Emacs を使い編集しているが、翻訳者のために良いツールを探しているのでおすすめがある方は教えていただきたい。

このスクリプトのポイントは何回実行しても.poファイル内のすでに翻訳済みのエントリは変更しないという点だ。 アプリケーションを更新したらスクリプトを実行し、.poファイル内の未翻訳のところのみ訳す、というサイクルを繰り返すことができる。.poファイルエディタは未翻訳エントリだけを簡単に編集できるというところも便利ポイントかな。

.po ファイルが翻訳されたら

$ ./po2strings.pl --po=resources/ja.po --strings=resources/Japanese.lproj/Localizable.strings src/*.m

などのようにしてiPhoneアプリから使える言語ファイルに書き出してやって完了。 ソースファイルを渡す必要があるのはスクリプトソースを見てもらえばわかるがただの手抜きである。

あ、ちなみにわざわざ NSLocalizedString(...) とか書くのは面倒なので、

#define L(s) NSLocalizedString(s, nil)

とかを定義しておいて L(@"Hello") とかでいけるようにしている。(もちろんxgettext.plはその書式にも対応している)

by typester / at 2011-03-04T15:45:00 / iphone / Comment

bREADER の PDF リフロー表示機能が素晴らしい件

BB2C というiPhone用の2chビューワアプリがあるのですが、このアプリは2chビューワとしての評価が高いだけではなく、その凄まじく軽快な動作からiPhoneアプリ開発者で知らない人はモグリだと言われるくらいの良アプリなのです。 最近その作者のブログ に電子書籍の話題が出ていて気になっていたのですが、どうやら次は電子書籍リーダーアプリを開発していたらしい。それが bREADER

iPad を買って以来書籍はPDFにして読むのがメインになっていて、大体はi文庫HDで、検索が必要なときはiBooksかMac上で読んでます。

この環境におおむね満足しているのだけど、電車の中で座れなかったときに本を読もうと思っても iPad を出すのはきびしいし、かといって iPhone だと PDF を等倍表示ではきびしいものがある。(Retinaならよめないことはないけど…) 拡大したら拡大したで、1ページを読むのにページ内をぐるぐるスクロールしつつ読まなくてはいけなくなり大変苦痛である。 文字を拡大したぶんだけページがずれていってくれるePubや青空文庫みたいな形式でなければiPhoneではきびしいなーと思っていました。bREADERをしるまでは!

この問題はがんばって紙の書籍をスキャンしてPDF化している現状が問題であって、出版社がePubみたいなので最初から電子出版してくれれば解決なんだからそれまで待とう…。 と普通の人は考えます。bREADERの作者はそういう発想はないらしく、力技でこの問題を解決しようとしているようです。

詳しくは先ほどリンクしたブログを参照していただくとして、手持ちのPDFで試してみた結果を紹介します。

まずこれがPDFをそのまま表示した時。(iPhoneの画面だとわかりにくいのでわざとコントロールを表示した画面にしています)

bREADER 通常表示

iPhoneの普通のPDFリーダーはみんなこんな感じに表示されてしまいます。Retinaディスプレイを搭載したiPhone4でやっとよめるというくらいですが、それでも小さくてつらいです。 つぎに bREADER がうまいことやってくれたあとの画面を見てみましょう

bREADER リフロー表示

このようにまるでePubかと思うような拡大処理をしてくれてものすごく読みやすくなります。 そしてこのアプリの動作自体も BB2C を彷彿とさせる軽快さで、サクサク読み進められます。これはPDF化をがんばってる人でiPhoneのような小さいデバイスで本を読みたいと思っていた人には夢のようなアプリですね!

ただし現状ではPDFがそのまま自動で上のようなリフロー表示(というらしい)になってくれるわけではありません。ちょっとごにょっと作業をして上げる必要があります。

bREADER で PDF をリフロー表示に対応させる手順

リフロー表示を行うにはあらかじめMacかPCで書籍の文字情報を解析してメタ情報ファイルを作ってあげる必要があります。 作者がサイト上でそのためのbrcというツールを配布してくださっています。 が、そのツールはPDFに対応していませんのでまずはPDFをPNGとかなんか画像ファイルに変換してあげるという作業が必要になります。

  1. PDFを連番のPNG画像に変換
  2. brcでメタ情報ファイルを作成
  3. zipでかためてbREADERに転送

ってなかんじになります。

PDFを連番のPNG画像に変換

これをどうやろうかとおもいまして、最初に Preview.app を試してみたのですがどうも1ページずつしかPNGに保存できないっぽくあきらめ、OSX の機能をつかった pdf2png を適当にでっちあげた。

https://gist.github.com/850786

こいつを

$ gcc -framework Cocoa pdf2png.m -o pdf2png

とかでビルドしてあげて、

$ ./pdf2png foobar.pdf ./foobar

みたいにすると foobar.pdf を 0001.png みたいな連番の画像にして foobar ディレクトリに保存してくれると言う感じ。出力先のディレクトリはあらかじめ存在している必要があります。 PDFのスケーリングがよくわからなかったので適当に設定してあり、そこは誰か良い感じに書き直してくれればと…! あと CGBitmapContextCreate の第一引数にNULLをわたしていてそこが OS X 10.6 依存です。それ以前のでうごかしたいときは malloc(w*h*4) したの食わせとけばいいです。

brcでメタ情報ファイルを作成

先ほど生成した連番PNGが入っているディレクトリを brc コマンドに食わせる

$ brc ./foobar

これでそのディレクトリに d.brd というファイルが生成されていれば成功。

zipでかためてbREADERに転送

$ zip -0 foobar.zip *.png d.brd

みたいにして png と d.brd がふくまれた zip ファイルを作り、あとはそれを iTunes で bREADER のデーターフォルダに転送してあげればOK。簡単ですね。

まとめ

と、ここまで絶賛してきましたが現状では、ページ上部に常に出ている章の名前とか欄外に脚注なんかがあったりすると表示がおかしくなったりします。 とはいえそれでもiPhone上ではこの表示モードを使おうと言うくらいのレベルに既になっていると思います。これはほんとにすごい。

今後、bREADERもbrcも便利になっていくだろうし、brcはいらなくなる可能性もあるし、非常に楽しみですね!

bREADER 現バージョンの不満点をあげるとすればアプリ内に時計表示がどこにもなくアプリを落とさない限り時間がわからない、ということくらいでしょうか。 自炊派も、業者派も、iPhoneでPDF(書籍)を読む人すべてにおすすめなアプリです!

by typester / at 2011-03-02T19:17:00 / iphone / Comment

iPhoneバックグラウンドでのソケット監視のサンプル

iOS4 からアプリケーションのバックグラウンド動作がサポートされているが、それらはだいぶ制限されたものとなっていて、通常のアプリケーションはバックグラウンドでは最大10分程度しか生存することが出来ない。

それを回避するために Info.plist に特殊なフラグをたてることで例外的に制限を解除することができるようになっている。そのフラグの種類は

  • audio
  • location
  • voip

であり、それぞれバックグラウンド音声再生、バックグラウンド位置情報取得、バックグラウンドでのソケット監視(voipの着信監視用)に対応している。 そのうち voip だけ今まで扱ったことがなかったので挙動確認のためやってみた、というのが今回のお話。

まず書いてみたコード:

https://github.com/typester/voip-socket-test

ソケットのハンドリングに libev を使っているので、ビルドを通すには $(HOME)/dev/iphone/lib/libev に libev がインストールされている必要がある。 また、AppDelegate.m の #define HOST inet_addr("127.0.0.1") という部分を自分のサーバーのIPになおしてあげるというのも必要。

んで、付属の server.pl が適当に作ったサーバーで、STDIN を読み込んで、入力があったらつながっているiPhoneにその内容を送信するというものになっている。 iPhoneアプリ側はバックグラウンド時にそのパケットを受け取ると受け取った内容をLocalNotificationで表示っていう感じ。

NSInputStream にたいして

[stream setProperty:NSStreamNetworkServiceTypeVoIP
             forKey:NSStreamNetworkServiceType];

とかしてあげとくだけで、アプリがサスペンドしたときには OS 側がソケットの監視を代理でやってくれる。 で、そのソケットにたいして何かイベントが起こるとアプリを起こして処理を戻す。アプリ側は通常のソケット操作だけ実装しておけばいい。

このアプリのようにネットワーク周りはCで処理している場合、ファイルディスクリプタを NSStream にアップグレードする必要があってそこは若干めんどう。 Cocoa::EventLoop でもおなじことをしているのでその XS 部分からコピペしてきた。

Skype のバッテリー消費が半端ないのでこの機能にたいしてあまり良い印象はなかったのだが、実際にやってみるとバッテリー消費は思ったより気にならない(いまのところ。あまり検証はしていない)。 おそらく Skype は頻繁にパケットを送受信しているから、アプリが頻繁に起こされ、結果ものすごい勢いでバッテリーが減る、ということなのだろうか。

とにかく、なかなかおもしろい機能。VoIP アプリ以外でもつかえればなー。

by typester / at 2011-01-28T14:35:00 / iphone / Comment

kAudioSessionCategory_PlayAndRecord のときの AudioRoute 文字列メモ

ドキュメントに載ってなくて、実際うごかしてみて文字列を知るという状況。 なので持ってない機種とかデバイスでどうなるのかわからないという問題があり不安。

ReceiverAndMicrophone

PlayAndRecord 時は iPhone ではデフォルトでこれになる模様。 通常のスピーカーではなく電話の受話口から音が出る。

HeadphonesAndMicrophone

ヘッドフォンさしたとき。

SpeakerAndMicrophone

iPad とか(たぶん最新以外のiPod Touchも)のように受話口がない端末ではこれがデフォルトっぽい。

デフォルト以外のルートに設定する

スピーカーに強制的に変更するには AudioSessionSetProperty でできる。

UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty(
    kAudioSessionProperty_OverrideAudioRoute,
    sizeof (audioRouteOverride),
    &audioRouteOverride
);

他のルートに変更する方法は undocumented。

ただこのうわがきはすでに SpeakerAndMicrophone なところで実行するとおかしくなっちゃう。 AudioSessionSetProperty するまえに AudioSessionGetProperty で現在の値を確認するようにすること。

あと、AudioRoute の変更は AudioSessionAddPropertyListenerkAudioSessionProperty_AudioRouteChange を登録しておけば監視できる。

by typester / at 2010-11-23T09:16:00 / iphone · audio · AudioSession / Comment

« Prev 1 2 3

(Page 1 of 3)