watermint.org

Takayuki Okazaki's blog

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - Java使いがソーシャルサービス開発の現場で思ったこと

寺田さんにつづき、Java Advent Calendar 2011の12月20日分です。明日はzinbeさんです。

さて今回はJava自体の話ではなくて、現在の勤務先でやっているようなソーシャルサービスの開発とJavaの関係について少し考察します。まず自分自身のスキルを簡単に紹介しておくと、Sun Microsystems時代には7年程度Javaに関連する仕事をしていました。その内容は金融業、流通業、製造業など様々な業種のサービスを支えるシステム構築や、トラブル解決のお手伝いという内容でした。その後、ACCESSに移ってすこしJavaから離れてObjective-Cを書いていました。現職ではObjective-Cも時々触りますがPHPがメインです。Objective-Cは実質1年、PHPも同じく1年ぐらいというところでしょうか。本格的にJavaを使うことから離れておよそ3年たちましたが、未だに一番詳しいのはたぶんJavaだと思います。
R0023024.JPG
幸か不幸か、2008年にSunを退社してからしばらくはOracleによるSun買収などもありJava自体のテクノロジー的な進歩が停滞していました。このため、世の中に流通しているJava技術はまだ僕の知っている頃のものとそんなに差分がないと思っています。今年に入ってOracle持ち前の推進力がようやくJavaに対して効果を見せ始め、大きな変化のタイミングに入ってきたなと感じています。

Javaは2000年頃から続くエンタープライズ時代からのしがらみもあり、その殻からしばらく抜け出せず、「クラウド」が当たり前になって、もはや必須になった状況にはうまく対応できずにいました。最近になってようやくJBossがクラウドのソリューションを発表したり、Java EE 7にはクラウドプラットホームを前提とした仕様が入るようになってきました。こういう状況を見ていると、数年以内にJavaで育ったソーシャルサービスが世の中に増えてくることはおそらく間違いないだろうと思っています。それは、SIをしていた会社がB2Cサービスに転身したり、Javaの資産を使ったWebサービスがいよいよ現実的にマススケールに対応できるようになるだろうからです。

一方、現在のソーシャルサービス開発の現場を見渡してみるとJavaはまだまだこの分野に踏み込めていないことがわかります。たとえば、GREEやFacebookはPHP、mixiやmobageはPerl、CookpadはRubyなどいわゆるスクリプト言語が開発現場での主流です。各社の求人も多くはこれらに関連するスキルを持ったエンジニアに対するものが中心であることからもよくわかります。
R0023026.JPG
これにはいくつか要因があると思いますが、今回は2つ紹介しておこうと思います。

ひとつめは、既存資産の問題です。ソーシャルサービスは各社とも個人レベルで小さく始めていたサービスが人気を呼び、拡大していった。という歴史を持っています。このため当然ながら、システムを支える多くの部分は開発当初に選んだ環境、2004〜2006年頃に価格的・技術的にリーズナブルであった選択肢をベースとしています。それが結果的にPHPであったり、PerlやRubyであるわけです。仮に、2011年の今、ソーシャルサービスを開発するのに最も適した言語がJavaであると判断したとしたとしても、わざわざいま開発環境を変え、PHPで書いてあるコードをJavaに移し替えることは労力や期間などを考えれば現実的ではないですし、費用対効果の面からも新しい環境に乗り換えることは十分説明がつかないでしょう。
R0023007.JPG
二つ目は、リリースサイクルの問題です。現在もJavaは多くのエンタープライズシステムで使われています。Javaはエンタープライズシステムのリリースサイクルと相性がよいと思っています。エンタープライズシステムは統計などをみるとおおざっぱには、3〜18ヶ月程度かけて開発され、12〜48ヶ月程度で新しいシステムにバージョンアップしたり、リプレースされているとおもいます。安定性を重視するシステムでは、バグ修正や、バージョンアップは数週間に1度程度であることがほとんどでしょう。一方、ソーシャルサービスはユーザの動向を見ながら、ほぼリアルタイムにシステムを更新することが求められます。Java EEアーキテクチャのように、コンテナ側で多くの状態管理をしてくれることはスケーラビリティーやパフォーマンスの観点から重要なのですが、サービスを止めずにシステムを部分的に置き換えたり、追加や削除するにはあまり向いていません。OSGiやJSR 277、JSR 294のような仕様は部分的にこれらの問題を解決できるかもしれませんが、運用フローまでのノウハウが蓄積されるまでにはまだ2〜3年単位の時間がかかるのではないかとおもいます。

以上、結論としてはややJavaに対してネガティブなものになりましたが、この現状をきちんと認識した上でテクノロジーとしてのJava、それを支えるコミュニティーが問題解決に取り組んでいければ、ソーシャルサービスの中でのプレゼンスをどんどん高めていけるのではないかと思いました。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - お客様の中に名探偵はいらっしゃいませんか?

なんて思ってたら、


!!!
お客様の中に名探偵の方はいらっしゃいませんか?><

ということで、iOS Advent Calendarのネタです。今回は、iOSデバッグ界隈の中ではマイナーと言われるか、一部のファンに熱狂的な支持!といわれるか絶妙なバランスを保ったDTraceでのデバッグを取り上げます。

DTraceはもともと、Sun Microsystemsによって開発されSolaris 10に搭載されたデバッグフレームワークです。現在は、FreeBSDやMac OS Xなどでも利用することが出来ます。詳しい説明は今回は省きますがTraceと名前がついていることから想像できる通り、プログラムの実行をトレースしてどのコードが、どういう風に実行されたか。といったことを調べることが出来ます。

こんなことはありませんか?

  1. 初期化処理がどうやらうまくいっていないようだが、どこまで実行されたか知りたい。しかし、Xcodeのデバッガでステップ実行・ブレークポイント設定などしながら確認するにはプログラムが大きすぎる。
  2. NSLogを埋め込んでどこまで実行されたか確認したいが、そもそもログを埋め込むのが面倒くさい。または、ソースコードをNSLogだらけにして汚したくない。

個人的にはXcodeのブレークポイント設定はなかなかよく出来ていると思っていて、多くのケースではそれで足りると思っています。

Xcodeでブレークポイントを設定してActionにSoundを設定、ブレークポイントは自動的に続行するように設定しておけばその該当箇所が実行されたかどうか音で聞き分けて調べることが出来ます。初期化関連の問題を探すときなんかは個人的にはよく利用しています。

さて、これでも十分なんですが設定したいブレークポイントが増えてくると音の種類も限定されますしどの音がどのブレークポイントだったかよくわからなくなります。Log Messageなどのアクションを設定してログ出力するのもいいのですが、網羅的にブレークポイント設定は面倒くさすぎます。

こういうときに威力を発揮するのがDTraceです。まずは手っ取り早くつかってみましょう。XcodeについているiOSのサンプルプロジェクトLazyTableImagesを題材にしています。Xcodeであなたのデバッグしたいプロジェクトを開いて、シミュレーター上で実行してください。出ましたか?

次にターミナルから次のコマンドを実行します。

$ ps ax | grep Simulator | grep -v iPhoneSimulator

すると次のようにiOSシミュレーター上で実行されているアプリのプロセスIDが分かります。下の例だと85894ですね。

85894   ??  SX     0:00.33 /Users/watermint/Library/Application Support/iPhone Simulator/4.0.2/Applications/993BF578-B81F-4B4C-97FA-B6B6843DD47E/LazyTable.app/LazyTable
88978 s004  R+     0:00.00 grep Simulator

さてこれが分かったら続いてdtraceを実行します。dtraceは他の人の実行しているプロセスなども丸裸にしてしまうなどでき、セキュリティー上の課題があるため一般ユーザでは実行できません。sudoをつかって実行しましょう。下記の85894の部分は調べたプロセスIDに読み替えてください。

$ sudo dtrace -p 85894 -n ‘objc$target:::entry { printf(“[%s %s]\n”, probemod, probefunc); }’

できましたか?できたらアプリを適当にタップするなど動かしてみてください。

このようにObjective Cで実行された関数がリアルタイムに出力されます。どうでしょうか?これでNSLogを仕込まなくてもトレースがとれるようになりましたね。でもちょっと出力が多すぎるようです。これではデバッグの役に立てるにはややしんどいです。出力を抑制してみましょう。

objc$target:::entryのところを、objc$target:クラス名::entryと書き換えてみましょう。ここではLazyTableImageにあるクラスのIconDownloaderとしましょうか。実際に実行するときにはあなたのプロジェクトにあるクラスをつかってください。

$ sudo dtrace -p 85894 -n ‘objc$IconDownloader::entry { printf(“[%s %s]\n”, probemod, probefunc); }’


出力が一気に減って、IconDownloaderだけになりましたね。ご想像の通り、関数名でもフィルタできます。

$ sudo dtrace -p 85894 -n ‘objc$target:IconDownloader:-indexPathInTableView:entry { printf(“[%s %s]\n”, probemod, probefunc); }’

便利ですね。これでたとえば、初期化処理のときに通らなければならない関数がちゃんと実行されているかどうか分かるようになりました。クラス名や関数名にはワイルドカードが使えます。たとえば、IconDownloader以外にIconButton、IconButtonLabelなどのクラスがあった場合にはIcon*というようにアスタリスクを指定するとIconで始まるクラス名にマッチします。

さて、デバッグしていて原因箇所がなんとなくわかってきたときに知りたいのはその関数がいつ、どこから呼ばれたか?です。呼び出し元を知りたいですよね。そういうときは、もうちょっと凝ったdtraceの使い方をしてみましょう。

次のようなファイルを用意して、trace.d というファイル名で保存しておきます

#pragma D option quiet

self int level;

objc$target:::entry
{
  printf("%*s[tid=%lld][%s %s]\n",
    self->level * 2, " ->",
    (long long)tid, probemod, probefunc);
  self->ts[probefunc] = timestamp;
  self->level++;
}

objc$target:::return
{
  time = timestamp - self->ts[probefunc];
  self->level--;
  printf("%*s[tid=%lld][%s %s] [%dms]\n",
    self->level * 2, " <-", (long long)tid,
    probemod, probefunc, time / 1000);
}

ではこれを実行してみましょう。

$ sudo dtrace -p 85894 -s trace.d

このように関数呼び出しをツリー状にしてダンプできます。後は必要に応じてクラス名や関数をフィルタしていけば良いでしょう。

さて、すこし駆け足になりましたがXcodeのデバッガをつかう場合と比べてだいぶ便利な機能があることがお分かりいただけたでしょうか?

コマンドラインからの実行に抵抗があるというかたはInstrumentsからもDtraceは実行できます。

さて、すこし駆け足になりましたが最後に制約事項を説明しておきます。dtraceはOS側に仕込まれたプローブという検出ポイントに対して働きます。このため、トレースしたい内容のプローブがOS側で実装されていることが必須です。このため、dtraceのプローブを持たないiOSにはdtraceを接続できません。必ずiOSシミュレータ上で実行しているアプリに対してdtraceを実行してください。

iPhoneやiPadなどのiOSが動作している実機に対しては別途従来通りのXcodeやgdbを用いたデバッグが必要です。

Mac OS XのdtraceにはObjective Cのためのプローブがいくつか用意されています。詳しくはman dtraceを参照してください。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - iOSプログラミング: 不透明なUINavigationBarを設定しつつも、背景のビューは座標を変えたくない, 補足

はてぶコメントで下記のようなコメントを頂きましたので、なぜ前エントリのようなやり方をしているかを補足させていただきます。

UIScrollViewのcontentOffsetでスクロール位置にオフセットを付けてバーにめり込んでいるように見せかけるのが正解だと思う。

たしかに、UIScrollViewをおいても同じことができます。ただ、それを言い出せばUIScrollViewでなくともUIViewControllerのビューにもうひとつUIViewを作ってその上にコンテンツをいろいろのせておいて、そのUIViewのオフセットを (0, -44) とかに設定しておいてもいいでしょう。この方法でもいいんですが、あえてこの方法を使っていないのには理由があります。UINavigationBarは必要に応じて表示したり消したりすることがあります。バーを表示しているときは UIScrollViewのオフセットを(0, -44)にして、非表示のときには (0, 0)に戻して、などと処理をすることは面倒なだけでなく、思わぬバグを招くことが多いのです。コメントを頂いたような方法を使っていないのは上述のバグを生まないようなコードにしたいというのが最大の理由です。可読性もあがりますし、あちこちに注意書きを書かなくてすみます。
またUIScrollViewで実際に何かをスクロールさせているような場合のときは、オフセットを(x, -44)にするだけでなく、frameサイズも適切に設定してやらないとpagingEnabled=YESのときスクロール時のストップ位置が思わぬところになってしまったり、より面倒くさい事態になりそうなので個人的にはこの目的のためにUIScrollViewを使うというのは好みではありません。
一方で前エントリで紹介した方法のリスクとしては、iOSの仕様がかわって挙動がかわってしまうことがあげられます。ただこのリスクは個人的には、オフセットを操作してやる方法でも大差ないと考えています。

最後に、どんな実装が最適なのかは制約事項とユースケース次第で状況によると思いますので「〜〜が正解だと思う」というのは一概にいえなくて、多種多様な実装方式のメリット/デメリットを押さえていくのがこれまたiOSプログラミング道だと思っています。いろんな実装方式があると思っていますので、いろいろ教えてもらえるとありがたいです。今回のようなコメントを頂くことで、仮説と検証もより深みを持ちそうです。id:rryuさん、コメントありがとうございました。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - iOSプログラミング: 不透明なUINavigationBarを設定しつつも、背景のビューは座標を変えたくない

Cocoa Touchには画面遷移をコントロールするために便利な、ナビゲーションコントロールという機構があります。ナビゲーションコントロールには、全ページに戻るためのボタンや、ページのタイトルを表示するためのナビゲーションバーがあります。さて、このナビゲーションバー(実体はUINavigationBar)、translucentプロパティをYESにすれば透明になり背景のビューは座標が(0, 0)から始まりますが、translucentが NO の場合には(0, 44)などから開始となります。

Screen shot 2010-10-15 at 0.16.44.png

translucentがNOであるような、UINavigationBarを表示するとUIViewController自体のoriginが(0, 44)などになる。

Screen-shot-2010-10-15-at-0.18.14.png

translucentをYESにして、透明にすれば問題はすぐ解決しますが、表現の問題として時には半透明でない方がよい場合が有ります。そういった状況に対処する方法です。UINavigationBarはtranslucentをYESにしておき、backgroundColorを設定してやればうまく行きます。

Screen shot 2010-10-15 at 0.24.24.png

ViewController側で次のようにコーディングすればよいでしょう。

- (void)viewDidAppear:(BOOL)animated {

self.navigationController.navigationBar.translucent = YES;

self.navigationController.navigationBar.backgroundColor = [UIColor blackColor];


こういう要求に対するコーディング、わかってしまえば簡単なんですが調べるのには結構時間がかかってしまうものなんです・・。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - iOSプログラミング: Xcodeの設定

開発環境の使いやすさ、特に「手になじむ感覚」は開発生産性だけでなく開発中のモチベーション維持に重要な要素です。このため、開発環境は多くの場合、使いやすくカスタマイズして利用されますし、ほとんどの開発環境はその声に答えるべく多くのカスタマイズをサポートしています。しかしながら、個人的にはカスタマイズは最小限とどめほぼデフォルトの状態を使うことをここ10年ほど実践しています。ほぼデフォルトの状態とは、再インストールして(設定のバックアップなしで)再設定が終わるまでに5分もかからず再現できる程度のカスタマイズ状態をイメージしています。このメリットは、再インストール作業が楽なことと、開発環境がバージョンアップしても設定の引き継ぎやプラグインの互換性で悩まなくてすむこと、他の人に操作方法を教えるときに標準的な方法を教えられること、複数のマシンで開発するときに設定を共有する手間が省けることなどがあげられます。本当にがっつりコードを書くことを考えれば十分カスタマイズした方が生産性が高いのですが、iOS向けアプリケーション開発では、コードを書くこと自体よりもコンセプトの設計、紙と鉛筆を使ったペーパーモックの作成、モデル/ビュー/コントローラーの設計に重点を置くべきでコーディング作業自体は相対的に少なくなり、がっつり開発環境をカスタマイズすることのメリットはさほど無いと感じています。さて、そんなことを書きながらもカスタマイズしている部分は少し有ります。それを紹介しましょう。

XcodeScreenSnapz001.png

まず画面のレイアウトです。XcodeはEclipseやNetBeansなどほかの開発環境と比べてかなり広く画面を使うため、画面の領域をどれだけ節約できるかが細かな時間の節約になります。レイアウトは3種類選ぶことができますが、個人的にはCondensedがおすすめです。

Screen shot 2010-10-14 at 1.26.48.png

DefaultやAll-in-Oneのレイアウトでは、ターゲットやファイルのペインとエディタが統合されていますが個人的にはこれらは別々の方が使いやすいと感じています。もともと俯瞰的にコードを眺めるためエディタのエリアは広くとりたいと思っています。このためには、エディタがウインドウの一部に統合されているより独立している方が便利です。Xcodeは一つのファイルに長々とコードを書くとパフォーマンスが劇的に悪くなる、また設計/管理の観点からもファイルは意味のある小さな単位に分割しておくべきですからファイルブラウザとエディタは分離していた方が心理的にも分かりやすいからです。

このほかのカスタマイズはキーボードショートカットの変更が一点。Developer Documentationの検索をFirefoxの検索ショートカットと合わせてCmd+Kに割り当てています。

Xcodeのカスタマイズは以上2点です。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - iOSプログラミング

iOS向けのプログラミングを始めたのが今年の3月中頃。初めてのiPhone/iPadアプリは4月2日リリース。iOS向けプログラミングを初めて半年ちょっとになりました。せっかくなので、忘れてしまわないうちに、何日かに分けてメモを残していこうと思います。まず最初のメモはiOSプログラミング全般について。

_DSC3196.jpg

iOS向けプログラミングでは、開発環境はMac上のXcode、開発言語はObjective-Cを使います。さらに実際のiPhoneやiPadといったデバイス上で自作アプリを動作チェックするためには有償の開発者登録(10,800円/年)が必要です。この開発者登録が高いと感じるか、安いと感じるかは人それぞれの感覚次第かと思いますが個人的にはアプリの審査と親切なフィードバック/iOS上に繰り広げられているビジネスモデルへの参加価値といった点から開発者登録は割安に感じるぐらいの価値があると思っています。

_DSC3181.jpg

さて、実際のプログラミングですがObjective-Cという、C++/Javaなどの言語の経験をバックグラウンド持っている自分としてはやや取っ付きにくい言語を使います。このことを取り上げてiOS向けプログラミングは難しいと言う人も時々見聞きしますが、個人的には言語の持つ文法やコンセプトの違いは最終的なプログラムにさほど大きな影響をもたらさないと思っています。開発生産性の違いを指摘する人もいますが、今や開発言語は言語自体の開発生産性だけで語れるものではなく、フレームワーク、開発環境など開発を取り巻く全体をみて判断する必要が有ります。では、iOS向けXcode、Cocoa Touch、Objective-Cの組み合わせはどうでしょうか。

_DSC3189.jpg

開発環境 Xcode は、EclipseやNetBeansなどモダンなJava開発環境に慣れた人にとっては力不足感を感じると思います。Microsoftの開発環境はさほど詳しくないので評価しづらいですが、すこし使った経験だけで判断してもVisual Studioは十分に洗練されており、Xcode は発展途上という言い方をされてやむを得ない差があると感じます。シンタックスエラーの検出、入力補完は今やリアルタイムに実施されるのが当然ですが Xcode にはそれが有りません。バージョン管理システムとの親和性もまだまだ改善の余地を感じます。Xcodeの力不足感、Objective-Cの文法への戸惑いはiOS向けアプリケーション開発にとって最大の心理的障壁といっても過言ではないようにも思えてきます。次期メジャーバージョン Xcode 4 Betaも少し試用しましたがようやくモダンな機能がいくつか追加されたものの、Eclipse、NetBeans、Visual Studioに追いつくにはまだ数年を要するのではないかと感じました。

_DSC3191.jpg

さて、一方でiOS向けフレームワークCocoa Touchはどうでしょうか。今まで主にJavaベースで、いくつかのリッチクライアント・フレームワークを使ってきましたがCocoa Touchほど洗練されたフレームワークは無いだろうという印象を持ちます。APIドキュメントの説明が不足しているが為に生産性を損なっていることも有りますが、枯れたMVCモデルを採用しつつもモダンに仕上がったAPI郡は圧巻です。iPhone/iPadはAppleがハードウエアから、OS、フレームワークまで一貫して製造しているためフレームワークがデバイスに最適化されているということは当然です。ハードウエア/OSの組み合わせに最適化されているからこそ、「特殊な状況下におけるバグ回避のコード」をほとんど書かずにすみます。全くゼロというわけではありませんが、今までに経験したハードウエア、OS、フレームワークの組み合わせの中では最も少ないと思っています。この効果は絶大です。Xcodeが押し下げている開発生産性を補って十分なメリットを感じます。

_DSC3339.jpg

機種依存やバグ回避のコードをほとんど書かなくてよいがために、フレームワーク上にバグ回避フレームワークを作り上げる必要が有りません。Cocoa Touchを直に使った方が生産性が上がり、パフォーマンスも損なわれません。Cocoa TouchのAPI郡は既に十分抽象化されているためこれ以上ラッパーやGlueコードを書くことは多くの場合無意味です。このため、iOS向けアプリケーション開発ではいかにCocoa Touchのコンセプトをつかみ、APIを正しく使えるかが開発生産性だけでなく最終的なアプリケーションコードの品質を左右するといって言い過ぎではないでしょう。

ググって調べた情報を鵜呑みにして開発するのではなく、しっかりとコンセプトの再確認と自身による検証がiOS向けアプリケーション開発では「急がば回れ」になる。ということがこの半年の開発経験による結論です。また、次回以降のメモではもう少し具体的な検証結果を残していくことにします。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - Mac OS X Snow Leopardを英語環境で使う

OSは普段使う分には日本語環境で十分なのだけれど、ときどき英語環境でないと困ることがある。そのなかで顕著なケースが、新しい開発用ソフトを使う場合。ここ半年ほど使っているiPhone/iPad向けアプリを開発するために使っているXcodeもその一つ。普通に開発している分には何ら問題がないのだけれど、初めて使うフレームワークや、次々と追加される新しい機能を使う上では英語環境にした方が都合がいい。新しい開発環境に関する情報は英語しかないし、メニューの名前も英語だから。下手に日本語化されているとどれかわからなくて困ることも。

Language settings

言語環境を変更するにはシステム環境設定から、言語とテキストで順番を入れ替えるだけ。?さて、英語環境にしたときに困るのが、いくつかのアプリケーションは日本語環境でなければ正常に動作しないこと。Illustrator、Photoshop、Safariあたりがそう。Illustratorはプラグインの読み込みにいくつか失敗し、Photoshopは実用上困ったことはないけれど一部メニューが文字化け、SafariはAccept-Languageがenになって一部Webが文字化け。そういうときには、個別にアプリケーションに対して言語設定を実施。Mac OS X 10.5 LeopardまではFinder.appから設定できたけど、Snow Leopardではできない模様。

Language settings for some applications

コマンドラインで次のように入力。

defaults write $(mdls -name kMDItemCFBundleIdentifier -raw /Applications/Adobe\ Illustrator\ CS4/Adobe\ Illustrator.app) AppleLanguage “(ja)”

のように入力してアプリケーションを再起動すれば完了。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - はやぶさ


イトカワから、無事かえってきたそうですね。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - 今日のTimeMachineバックアップ, 急展開!

timemachine 2010.06.09

日曜日から実行しているTimeMachineによるバックアップがそろそろ終わりそうです。バックアップが10%ぐらい終了したあたりから、急に7MBytes/秒程度のスループットが出るようになりました。

timemachine 2010.06.10

何かのタイミングで、プログレスのダイアログが出なくなってメニューバー上にしか出なくなりました。あと残り約40GB。この分だと明日の朝には終わってそうです。

Share on Facebook
Share on GREE
このエントリーをはてなブックマークに追加
はてなブックマーク - iPad/iPhone電子書籍アプリ「クロノス日本版5月号」

Chronos 2010 May

電子書籍アプリ「クロノス日本版2010年5月号」が有償にてダウンロードできるようになりました。時計好き、メカ好きの方には心をそそられる内容がたくさん入っていますよ。

クロノス日本版5月号