Previous month:
December 2009
Next month:
February 2010

今や「聴く、観る、遊ぶ、読む、撮る」のすべてに関わるApple

 iPadに関しては、あまりにも私が予想した通りだったのであまり書くこともないが、「聴く、観る、遊ぶ、読む、撮る」という普通の人たちの生活のネットやモバイル・コンピューターが関わってくるなかで、これだけ確実に完成度の高い製品を出してくるAppleには、本当に関心してしまう。

 ジョブズ自身もプレゼンで述べていたが、「MacBookとiPhoneの間にプロダクト・カテゴリーはあるか?」という疑問は、Apple内部で長いこと検討されて来たことは明確である。「とりあえず出して市場の反応を見る」戦略とは大きく違い、「なぜこんな商品が必要か?」「なぜAppleがこの商品を出さなければならないのか」と考えに考えた結果、インパクトの強いものだけを出してくるのがAppleである。

 最近の唯一の失敗と言えば、Apple TVだが、これもカテゴリーとしては決して間違ったものではなく、タイミングとディール不足が原因である。米国での家庭への光ファイバーがもう少し普及し、コンテンツ会社とのディールが進んだ段階で、再び本気のローンチをかけてくると私は観ている。

 最近では噂にもならなくなって来たのが、iCamera。「撮る」という部分に関しては、Appleとしてはぜひとも抑えておきたい分野ではあるが、「なぜAppleが出さなければならないのか」という部分に関しての答えがまだ見つかっていないのだろう。結局のところ、Appleとして差別化がはかれる部分がソフトウェアだったりユーザー・インターフェイスであることを考えれば、レンズの品質が大きくものを言うハイエンドのマーケットにAppleが進出する意味はないし、ローエンドには、iPhoneがある。唯一やり残したことがあるとすれば、iPod touchにカメラを搭載することぐらいだろう。

 iPadがどのくらい売れるかについては予想するのは難しいが、少なくともハードはAmazonのKindelよりは売れるだろうし、eBook販売そのものでも一気にトップに躍り出る可能性は高い。既に数百万人規模のiTunes storeで音楽やアプリを買っているユーザーにとって、iPad向けのeBookの購入は心理的な障壁がとても低い。

 ちなみに、Appleの強さでもっと注目を集めるべきは、Apple StoreのGenius Barである。あれだけ便利なサービスを提供してくれるからこそApple商品を買い続ける、というユーザーは着実に増えており、これがボディーブローのように効いて来る。

 先日も、Bootcampのパーティションを切ったMacBookが不調になり、一度ディスクを初期状態に戻そうと考えたのが、パーティションを切り直す方法がすぐには見つからなかった上に、再インストールすべきOS-Xのディスクも見つからない。仕方がないので、近所のAppleストアでGenius Barでのサービスを7:45pmに予約して行くと、その場でわずか数分でOSの再インストールまでしてくれたので本当に助かった。保証期間の過ぎたMacBookにまでこのサービスをしながらちゃんと利益があがる仕組みが出来ているところがApple Storeの脅威だ。

 Best Buyで買ったSony製のノートパソコンだったりすると同じようなサービスを受けるためには、Best Buy/Sony/Microsoftの間でたらい回しにされるのでコストも労力も並大抵なものでは済まない。「OSが入っていたディスクが見つからない」ケースなんて最悪だ。

 それも別に決してSonyがダメな会社という話ではなく、WindowsをMicrosoftからライセンスして、(薄利多売でサービスで儲けるしかない)Best Buyを通して販売しているという構造的な問題だから難しい。Appleが瀕死状態だった90年代には、「独自OSはコスト的に見合わない」「Appleはソフトウェア・メーカーになるべき」とさんざん批判されて来たわけだが、ここに来て「独自OS」が大きなプラスになっている点はなんとも皮肉だ。

 しかし、またまたiPhoneに対抗したAndroidケータイと同じく、Chrome OSを搭載した完成度の低いタブレットPCとかがたくさん出て来ると思うと、少しうんざりする。「その他大勢」はもう良いから、本気でAppleと対抗できるビジネスを展開できる所に出て来て欲しい。市場のバランスを考えれば、そろそろ、「MicrosoftとDellが家電ベンチャーをジョイントで設立」「Palmが投資銀行から巨額な資金を調達してNokiaのスマートフォンビジネスを買収」なんて動きが出ても良い頃だと思うんだがどうだろうか。


無名関数を使った非同期通信のススメ(JavaScript)

 ここ最近はブラウザーの上で動く思いっきりRIAなアプリケーションを書いている私。こと通信の部分になると JavaScript での開発効率が、C++/Java/Objective Cなどと比べて格段に高いことをつくづく感じている毎日なので、今日は、そのあたりを少し解説してみようかと思う。

 サーバーのAPIにアクセスするプログラムを書く方法は色々とあるが、「サーバー上の特定のURLにHTTPでアクセスして結果をXMLやHTMLやJSONで受け取る」というケースに限定すれば、基本的に3つのパターンに分けられる。

1. 同期通信

result = urlfetch.fetch("http://www.google.com/")
if result.status_code == 200:
  doSomethingWithResult
(result.content)

 その書きやすさのために、実務経験の浅いプログラマーに好まれるのがこのパターン。上の例を見ても分かるように、「プログラムは上から順番に一行づつに実行して行く」というコンセプトとマッチしているので、プログラムを書くのも簡単だし、読むのも楽だ。それに加えて、すべてのコンテキスト(ローカル変数だとか、コールスタックだとか)が保持されたままなので、関数の中でサーバーから取得したデータをさらに加工して、それを関数の戻り値として返す、などが自然にできる。

 このパターンの一番の問題は、実行スレッドが通信中に解放されないことである。UIスレッドからこんなプログラムを実行すればユーザーインターフェイスが無反応になってしまうし(初期の Microsoft Outlook が典型的な例)、シングルスレッドのアプリケーションから複数の通信を同時にすることが不可能になる。

 「それを回避するためにマルチスレッドがある」と言う人もいるが、ほとんどの場合その考え方は誤りである。安易な考えからマルチスレッドを導入したためにデッドロックに苦しんだり、スレッドを作りすぎてパフォーマンスが極端に落ちる、などの失敗例は星の数ほど見て来た。MicrosoftのDCOMも、当初の実装は「プロセス間通信のたびに一つスレッドを作りそれに同期通信をさせる」というアーキテクチャだったため、ExcelからWordに何かをCut&Pasteするたびにスレッドがそれぞれのプロセスに7個ずつ作られる、という悲惨なものであった。

 ちなみに、この「通信をバックグラウンドでさせるためにスレッドを作る」という過ちは、ある程度経験を積んだプログラマでも犯しやすい間違いなので注意した方が良い(プログラミングの入門書などでそんなパターンを奨励しているものまであったりするので困ったものだ)。

2. 非同期通信(コールバック・オブジェクト方式

 そんな同期通信の欠点を補うために導入されたのが、非同期型の通信である。

NSURL* url = [NSURL URLWithString:"http://www.google.com/"];
NSURLRequest * request = [NSURLRequest requestWithURL:url ...];
MyDelegateClass* delegateObject = [MyDelegateClass ...];
NSURLConnetion* connection = [NSURLConnection connectionWithRequest:request
delegate:[delegateObject];
...
 

// MyDelegateClass.m ファイル
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
doSomethingWithResult(data);
}

 これは、Objective Cの例(iPhone OS)だが、connectionWithRequest:delegate:メソッドは、単に「通信の開始」を指示するだけで、サーバーからのデータをまたずにすぐ処理を戻す。そして、実際の通信は非同期にバックグラウンドで行なわれ、通信結果はコールバック用に渡したオブジェクト(delegateObject)の connection:didReceiveData:メソッドに非同期に渡される。

 この仕組みを使えば、たとえUIスレッドからこのコードを実行しても、ユーザーインターフェイスがロックすることもないし、複数の通信を平行して同時に実行することも可能になる。

 このパターンの一番の欠点は、プログラムが非常に読みにくく(つまりメンテナンスしにくく)なる点である。delegateObjectのメソッドは、上のコードとは離れたところ(多くの場合、別のファイル)に書かれており、「処理を目で追う」ことがとても難しくなる。コンテキストもdelegateObjectを介して明示的に渡さねばならず、同期通信では3〜4行のコードで書けていたものが、非同期通信を使うと新しくクラスを導入した上で複数のファイルにまたがる百行を越すコードを書かねばならない、などということはしばしばある。

 私が、WindowsやiPhone OS上でC++やObjective Cで書いたプログラムは、基本的にはすべてこのパターンで書かれているが、そのたびに「プログラミング効率の悪さ」にイライラしてきたことも事実である。

3. 非同期通信(無名関数)

 上の非同期通信プログラミングの持つ「プログラミング効率の悪さ」を一気に解消してくれるのが、クロージャと無名関数をサポートする言語(JavaScriptやRuby)である。

$.get("http://www.google.com/", null, function(data, textStatus) {
doSomethingWithResult(data);
});

 このパターンの利点は、上の例(JavaScript上でjQueryを使って通信をしている例)を見ただけで一目瞭然である。非同期通信でありながら、同期通信と同じく、何をやろうとしているかが一目で分かる。クロージャを使えば、新たなクラスなど導入せずに、コンテキストを渡すことも容易である(クロージャ内の変数に直接アクセスするだけで良い)。上の二つのパターンの良いところを持ち合わせたのが、このパターンだ。

 ◇ ◇ ◇ 

 以上が、このエントリーの冒頭で「こと通信の部分になると JavaScript での開発効率が、C++/Java/Objective Cなどと比べて格段に高いことをつくづく感じる」理由だ。「JavaScriptプログラマーは、クロージャと無名関数が自在に使いこなせるようになって、やっと一人前」と言われる理由もここにある。 


UIプロトタイプ:autocomplete (jQuery plug-in jSuggest)

 昨日に引き続いて、今日も作成中の Google App Engine アプリ用のUI部品の作成。HTMLの一部に記述された(もしくは別途JSONで取得した)ワード・リストの入力を autocomplete を使って簡単にしようという試み(Google Suggestのようにダイナミックにリストを取得する必要はない)。

 そこで、まずは既存のライブラリ・プラグインの調査から。必要とする人も多いようで、少し調べただけで20個ぐらい見つかる。デモを見て5つに絞ってからそれぞれのソースコードを解析。例によってどうしようもない品質のコードもあるので、結局のところたどり着いたのは、比較的コードがきれいなこの二つ。

 どちらかをそのまま使っても良かったのだが、どちらもリストをダイナミックにサーバーから取得するシナリオに最適化されている点が気に入らないし、コーディング・スタイルが私のものと違いすぎる。ということで結局一から作り直すことに。

 例によって「HTMLはView、JavaScriptはController」というモデルを厳密に守るためには、候補のリストはHTMLの一部に記述できなければいけない。

        <div class='suggest'>

            <input type="text" name="month" id="month" autocomplete="off" />

            <ul>

                <li>January</li>

                <li>Februrary</li>

                <li>March</li>

                <li>April</li>

                <li>May</li>

                <li>June</li>

                <li>July</li>

                <li>August</li>

                <li>September</li>

                <li>October</li>

                <li>November</li>

                <li>December</li>

            </ul>

        </div>

 もちろん、スタイルシートには、".suggest ul { display:none }" と書いて初めは隠しておく。

.suggest ul {

    color: #333;

    display: none;

    width: 15em;

    position: absolute;

    border: solid 1px #777;

    background: white;

    -webkit-box-shadow: 3px 3px 5px #888;

}

.suggest li {

    list-style: none;

    cursor: pointer;

}

.js_selected {

    background: #ccf;

}

.js_match {

    color: #000;

    font-weight: bold;

}

.js_hover {

    background: #99f;

}

 JavaScriopt側は以下の三行。

$(document).ready(function() {

    $('.suggest').JSuggest();

});    

 実際に動作するデモは下に貼付けてあるのでお試しいただきたい(いつものように、Safari4, Firefox3.5, IE7, Opera9で動作確認済み)。

 一番苦労したのが、Operaブラウザー上で、リストから一つ選択するためにEnterを押した時にformをsubmitしてしまうという問題を回避する部分。jQueryのkeydownイベントにfalseを返してもなぜかsubmitしてしまうのだ。同じ問題に直面した人も多かったようで、それを参考に解決。

 ちなみに、このプラグイン(ソースはこちらーMITライセンス、ドキュメントなし)を作るのに費やした時間は約9時間。既存のライブラリの調査に3時間、コーディングに4時間、各種ブラウザーでのテストとバグ取りに2時間、というところか。結構大きめのウェブアプリを1ヶ月で作ろうとしている中で、たった一つのUIライブラリのために丸一日費やすのは厳しいところだが、こういった部品レベルで手を抜くと後々痛いめに合うのでしかたがない。アプリへの組み込みは明日だ。


プロトタイプ:AJAXで改良するフォーム入力

 ここのところ、Google App Engine上でアプリを作っている私だが、iPhoneアプリとかを作り慣れている私としては、単純なHTMLページの組み合わせでUIを作るというのでは面白くない。そこで、サーバーがModel、クライアントがViewとControllerというアーキテクチャととことん追求してサービスを作っているのだが、そのためにはさまざまなUI部品を作らなければならず、そこにやたらと時間がかかっている。

 始めた当初は、「今はオープンソースの時代だからUI部品もオープンなものを集めてくれば済む」と軽く思っていたのだが、実際に使おうとすると不必要に複雑だったり、汎用化されすぎていたりしてそのままでは使えないものが大半。結局のところ、そのまま使える品質のJavaScriptライブラリはjQueryのみで、それ以外は、(1)オープンなものを元にシンプルなものを作り直す、(2)スクラッチでゼロから作る、のどちらかになっている。

 ということで、今日紹介するのは、色々と既存のものを調べた結果自作することになった SmartField というJavaScriptライブラリ(JQueryプラグイン)。フォーム入力を分かりやすくするために、入力前にグレーのテキストを表示しておく Watermark の機能と、各入力項目ごとに複数の Validation 関数とエラーメッセージをクライアント側に用意しておき、大半のエラー処理をクライアント側でする、というライブラリである。

 とにかく、HTML+CSSがView、JavaScriptがControllerという関係を厳密に保とうとすると、Watermark として表示するテキストや、エラーメッセージはHTML側に置く必要があるため、そこからデザインを始めた。具体的には、

        <div class='smart' id='field1'>

            <span class='sf_overlay'>ここにパスワードを入力</span>

            <input type="password" name="password" id="password" class="password" >

            <span class='sf_messages'>

                <span>アルファベットと数字のみが使えます</span>

                <span>少なくとも4文字以上必要です</span>

            </span>

        </div>

と書いておけば、「ここにパスワードを入力」という文字列がWatermarkとして表示される。それに加え、二つあるValidation関数それぞれに、「アルファベットと数字のみが使えます」と「少なくとも4文字以上必要です」が対応している。

 ちなみに、Watermarkとエラーメッセージのテキストは当初は隠しておく必要がある(display:none)。スタイルシートはこんな感じだ(sf_overlayのposition:absoluteとz-index:2は必須。paddingとfont-sizeはWatermarkテキストをinputフィールドの上にきれいに表示するためのおまじない...ここはアプリごとに微調整が必要かも知れない)。

.sf_overlay {

    position:absolute

    color:#777;

    padding-left:4px;

    padding-top:2px;

    z-index:2;

    display: none;

    font-size: 90%;

}

.sf_messages {

    color: red;

    display: none;

}

 そして対応するValidation関数は、こんな感じで記述する。

    $('.smart').SmartField();

    $('#submit').click(function() {

        return

            $('#field1')[0].sf_validate([

                function(v) { return /^[a-zA-Z0-9]*$/.test(v); },

                function(v) { return v.length >= 4; }

            ]) == 0);

    });

 最初の行で、フィールドを「SmartField化」し、次の行で Submit ボタンが押された時に Validation 関数を実行するように指定する。どこかにエラーがあれば、対応するエラーメッセージを表示した上でSubmitはキャンセルされる、という仕組みだ。sf_validate() メソッドに関数のアレイを渡しているが、これが Validation 関数。それぞれが HTML 中のエラーメッセージに対応している。

 ちなみに、このライブラリ(ソースコードはこちら)はオープンソース(MITライセンス)なので自由に使っていただいて結構だが、ドキュメントはまだ書いていないので、サンプルやソースを参考にして使っていただきたい。

 下に貼付けたサンプルは、作っているアプリのパスワード設定画面の部分を抜き出したもの(Watermarkを前提に、ラベルを排除するなどの工夫がしてある)。極端に短いパスワードや特殊記号を入力してエラーがどんな感じで表示されるか試していただきたい。一通り、Safari4, Firefox3.5, IE7, Opera9 では動作確認をしてあるが、万が一バグ(Watermarkが見えない、など)を見つけた方はコメント欄などで報告していただけるとありがたい。