2007-09-04
目次
りんご味Ruby 第10回 藤本 尚邦
前回は、Rubyのブロック付きメソッドによる繰り返しの抽象化の効果を示しました。今回は、I/O(ファイルやソケットとの入出力)への読み書きアクセスを題材として、ブロック付きメソッドによるシステム上のリソースへのアクセスの抽象化とその効果を示します。
■ (1) I/Oの開け閉めを直接プログラムする
ファイルへの読み書きやTCPソケットによる通信では、ほとんどの場合、以下のような手順で処理が行なわれます。
(1) まずI/Oを開く
(2) I/Oを介してデータを読み書き処理する
(3) 最後にI/Oを閉じる
まず、この手順を直截的にプログラムしてみましょう。
-----------------------------------------------
require 'socket'
require 'rss/2.0'
io = TCPSocket.open("www.mosa.gr.jp", 80) # I/Oを開く
io.write "GET /?feed=rss2 HTTP/1.0\r\n\r\n" # HTTP GET要求を送信
rss = RSS::Parser.parse(io, false) # 受信しながらRSSをパース
io.close # I/Oを閉じる
rss.items.each { |i| puts i.title } # 結果を表示
-----------------------------------------------
このプログラムは以下のような処理をしています。
・モサのWebサイトにTCP接続する
・HTTPプロトコルのGETを送信し、RSSデータを要求する
・RSSデータを受信しながらパースする
・接続を閉じる
・パース結果の各エントリのタイトルをプリントする
同時に開くことのできるファイルやソケットの数にはシステム上の制限がありますから、不要になった時点でそのI/Oは閉じるべきでしょう。しかし、上記のようなプログラムで I/O を閉じる部分を書き忘れてしまうというのはありがちな話だと思います。書き忘れなかったとしても、RSSパース時に例外が発生したりしたら、I/Oは開きっぱなしのままプログラムの実行は外に移ってしまいます。
■ (2) I/Oの開きっぱなしを避ける – ensure の導入
書き忘れはどうしようもありませんが、例外の発生によるI/O開きっぱなし問題には解決策があります。例えば Java の finally
try { ... } finally { ... }
のようなものを使う方法が考えられます(何が起ころうともtry節から出るときには必ずfinally節を実行する)。Rubyにも、beginとensureを使う同様の構文があります。さきほどのプログラムを改良してみましょう。
-----------------------------------------------
require 'socket'
require 'rss/2.0'
io = TCPSocket.open("www.mosa.gr.jp", 80) # I/Oを開く
begin
io.write "GET /?feed=rss2 HTTP/1.0\r\n\r\n" # HTTP GET要求を送信
rss = RSS::Parser.parse(io, false) # 受信しながらパース
ensure
io.close # I/Oを閉じる
end
rss.items.each { |i| puts i.title } # 結果を表示
-----------------------------------------------
begin節(beginとensureで挟まれた2行)の実行中に何が起きようと、ensure節(ensureとendで挟まれた1行)は必ず実行されます。つまり、ioは必ず閉じます。
I/Oが閉じられるようになって少し安心しました。このように書いている時点でI/Oを閉じることを意識しているわけですから、それを書き忘れることはおそらくないでしょう。
■ (2) I/Oの開け閉めを抽象化 – ブロック付きのopen呼出し
ところで、このプログラムは少し冗長な気がしませんか? とくに、I/Oの開閉に関する部分は、I/Oにアクセスするどんな目的のプログラムであろうとさほど変わるものではありません。言い換えると、TCPソケットで通信するプログラムには以下のようなパターンがあります。
io = TCPSocket.open(ホスト名, ポート番号)
begin
I/Oアクセス処理 ...
ensure
io.close
end
何度も同じようなパターンが出てくるときは、それを出来る限り抽象化して繰り返さないようにするのが正しいプログラマの姿勢(DRY原則-後述)。ここでブロック付きメソッド呼出しの登場です。TCPSocketクラスのクラスメソッドであるopenは、I/O(ソケット)へのアクセス処理をブロックとして渡すことにより、上記のようなパターンのI/Oの開け閉めを裏でやってくれます。この場合、ブロックの実行結果がopenメソッドの返す値となります。
TCPSocket.open(ホスト名, ポート番号) {|io| I/Oアクセス処理 … }
では、ブロック付き呼出しでさらにプログラムを書き換えてみましょう。
-----------------------------------------------
require 'socket'
require 'rss/2.0'
rss = TCPSocket.open("www.mosa.gr.jp", 80) { |io|
io.write "GET /?feed=rss2 HTTP/1.0\r\n\r\n"
RSS::Parser.parse(io, false)
}
rss.items.each { |i| puts i.title }
-----------------------------------------------
とてもすっきりした上、I/Oの閉じ忘れも書き忘れも起こりえないプログラムになりました。
ここまで、TCPSocketを実例としてI/Oへの読み書きアクセスの抽象化を説明しましたが、これはIOクラス(TCPSocketの上位クラス)でサポートしている機能です。ファイルI/Oに関しても同様に使うことができます。もちろん、このような抽象化をサポートするブロック付きメソッドをプログラマが定義することもできます。
■ [補足] HTTPアクセスについて
今回、説明の都合からTCPSocketを直接使いましたが、実際のRubyプログラムでHTTPプロトコルを使う場合には、Rubyに付属しているライブラリの net/httpあるいは単純な GET だけであれば open-uri (RSSリーダで使っているもの)を使うのが手軽かつ安全で良いでしょう。
■ [補足] DRY原則
DRY = Don’t Repeat Yourself の略。「同じコードの繰り返しは抽象化してひとまとめにしよう」というプログラミング上の原則のこと。「達人プログラマ」(ISBN-13: 978-4894712744)という本などで紹介されています。
藤本裕之のプログラミング夜話 #121
相変わらず暑い日が続くが皆さん元気にハタライておられますか。さて本連載は今回から新シリーズ、タカハシ編集長と協議の結果、ウインドウに付属している「ツールバー」というのはどうやって出すのだ? というのをやることになった。ツールバーって分かるよね? タイトルバーのすぐ下にあって、例えば Finder のウインドウならSpotlightの検索ボックスとか表示切り替え(アイコン/リスト/カラム)ボタン、Xcodeのプロジェクト・ウインドウなら「ビルド」とか「ビルドして実行」とかのアイコンが表示されているあそこのことである。
まずはXcodeで新規にプロジェクトを作成し、MeinMenu.nib をダブルクリックして Interface Builder を起動してみていただきたい。デフォルトで用意されるウインドウを選択し、Inspector で Attributesタブを選ぶとこれに関係ありそうな項目は一個しかない。下から2番目にある「Unified title/toolbar look」というやつで、NSWindowの時に説明したと思うがこれは「タイトルバーとツールバーを境目のない遠近両用眼鏡のような外見にするかどうか」のスイッチである。……早い話があるウインドウがツールバーを持つかどうかはここでは指定できない。
次にやはり Inspector で Connectionsタブを選んでみる。するとここのOutlet のなかに、おおありますね「toolbar」というのが。つまりなんらかのオブジェクトをこの Outlet に連結すればこのウインドウに突如ツールバーが出現する、仕掛けになっているのだな。
ところが、だ。やってみると分かるがこの Outlet に何か(例えばツールバーくらいのサイズにしたNSViewとか)を連結しようとすると無情にも「toolbar must be of type NSToolbar」ちうメッセージが出て繋がってくれないのである。ほんぢゃその NSToolbar というタイプのオブジェクトはどうやって作るのか? 普通この手のオブジェクトはパレット・ウインドウからドラッグ&ドロップすれば作れるもんなのだが、パレットのどこを探してもツールバーらしきものはないんですな。
というわけで結論、現在のところツールバーは Interface Builder を使って作ることができない、のである。あ、わざわざ「現在のところ」と断ったのは、この10月にリリースされる新OS、Leopard に付属する開発ツールでは、Interface Builder もバージョンアップされ、もしかするとそれが可能になる
かも知れないから、ね。つうかオレとしてもなって欲しいんだけどさ……。
ではどうするか。しょうがないから全部コードで書くのである。そう嫌な顔
をしてはいけない。昔はみんなそうだったでしょ? とりあえず恒例、
MainMenu.nib のウインドウで NSApplication のサブクラス MyApplicationを作り、こいつの Outlet に「window」(クラスはNSWindow)と、「report」(クラスはNSTextView)を追加する。デフォルトで用意されているウインドウに NSTextView をひとつパレットからドラッグ&ドロップして適当な大きさに拡げ、これを MyApplication の「report」に、ウインドウ自体を「window」に連結したら保存してひとまず Interface Builderは終了。
最初の目論見としてはツールバーにアイコンを一個表示し、それがクリックされたらreportに「クリックされたぞ」というレポートを表示したい。あ、アイコンを用意しなくちゃいけないか。インターネットから適当な絵を探してきてもいいが、面倒くさかったら /Libraries/User Pictures/ の下にユーザアカウントを作るときに選べる tiff ファイルが入ってるからネコとか犬とか好きなのを選んでプロジェクトに追加すればいい(「ファイル」タブの「Resources」の下にドラッグする)。
これで材料はそろったので MyApplication.h を開き、ツールバーを管理するのに必要なインスタンス変数を以下のように定義する。クラスはNSMutableDictionary。
NSMutableDictionary* toolbarItems;
あとやることは「アプリケーションが無事にラウンチされたよメッセージ」であるfinishLaunching: のタイミングでツールバーを生成し、それをウインドウのOutlet にセットする……ってもそれがなかなかの難物なんだけどね。以下は次回といたします。ほんぢゃ。
(2007_08_30)
高橋真人の「プログラミング指南」第119回
〜XcodeによるPowerPlant X入門(10)〜
こんにちは、高橋真人です。早速始めます。
今回は、前回の最後に載せたコードについての解説です。
実際に打ち込んだコードを走らせてみましたか? アプリケーションの動きを確認して、前のものと比較してみるといくつかの違いがあるのに気付くと思います。
いちばんの違いは、メニュー項目の表示部分です。File、Editの各メニューを開いて確認してみると分かりますが、NewとClose(いずれかのウインドウが開いている場合)以外の項目は、すべて無効化されています。
前回までのものは、FileメニューとEditメニューですべての項目が選択できる状態になっていました。しかし、上記の2項目以外について、今のアプリケーションでは実際にそれらを実行する処理はどこにも存在しないので、本来なら無効化しておく必要があります。
余りに身近な存在であるために、とかく軽視されがちなメニューの表示処理ですが、コンテキストに応じて表示状態を頻繁に変更する必要があるため、案外煩雑なものなのです。
Cocoaでは、フレームワークの柔軟性を生かして、なかなかに巧みなメニューハンドリングの仕組みが用意されています。私もオブジェクト指向、非オブジェクト指向の様々なシステムでメニューハンドリングを見てきましたが、Cocoaの処理のスマートさは群を抜いていると思います。
ご存知のようにCarbon自体はオブジェクト指向のフレームワークでないため、PowerPlantXのようなフレームワークは、もともとCarbonが持っているメニューの仕組みをうまくオブジェクト指向でくるんでスマートにする使命を負っています。オリジナルPowerPlantでは、Mac OSの複雑な歴史的背景のために複数のアプローチを用意していましたが、PPxでは、Carbonイベントモデルが用意するコマンドイベントをそのまま利用する形を取っています。具体的には、前回説明したPPxCommandEvents.hに定義してあるいくつかのクラスでこれらを解決しています。
前回も触れましたように、このヘッダに定義してあるクラスは、PPxのコマンドハンドリングの中核とも言うべきところなので、全部を説明しようとすると、どうしても話が深く、複雑になってしまいます。そこで、当連載では使い方の部分に徹して説明をします。動作原理の部分に興味のある方は、別途C++の解説書などを参照してください。
さてそれでは、再度クラスの定義を見てみましょう。MyApplicationの定義部分を再掲します。
class MyApplication : public PPx::Application,
public PPx::CommandConverter,
public PPx::SpecificMenuCommandEnableDoer
MyApplicationが継承しているクラスの二番目にCommandConveterがあります。これは、これから使うクラスを使うための裏方のクラスでして、「とりあえず、加えておくものなのだ」と覚えておいてください。
さらにそのあと,似たような名前のクラスが二つ続いています。 SpecificMenuCommandEnableDoerとSpecificMenuCommandDoerです。
まず、SpecificMenuCommandEnableDoerの方ですが、これはその名の通り「特定のメニューコマンドを有効にするもの」ということで、つまり、クラス名の直後にある<...>の部分で指定したコマンドが割り当てられているメニュー項目を有効にする機能を持っています。
たとえば、今回の一つ前のPPxForXcode02でのMyApplicationの定義は以下のようになっていました。
class MyApplication : public PPx::Application,
public PPx::CommandProcessDoer
{
(残りは省略)
もう忘れてしまったかもしれませんが、このアプリケーションを走らせた時にメニュー項目はほとんどすべてが有効になっていました(OSが管理してくれるWindowメニューは除く)。この部分を「適切に」するために、このSpecificMenuCommandEnableDoerを使うのです。
理屈としては、MyApplicationが継承するクラスに、たとえば
public PPx::SpecificMenuCommandEnableDoer
というのを加えることで、New…の項目が有効になります。
「え? もともとNew…は有効だったでしょ?」と思うと思いますが、それは結果的な話でして、あくまでそれ以外のメニューが有効であったことの方が、ここでは「普通ではない」のです。
その証拠に、Interface Builderですべてのメニュー項目を無効にした状態にしてから走らせてみれば、New…だけが有効になっているのが分かるはずです。
ここでちょっと面白い実験をしましょう。
PPxForXcode02をフォルダごと複製して(前のものを残しておく必要がなければ、複製しないでそのままいじっても構いません)、MyApplication.hのクラス定義の部分を少し書き換えます。
もともと、
class MyApplication : public PPx::Application,
public PPx::CommandProcessDoer
{
こうなっているのを
class MyApplication : public PPx::Application,
public PPx::CommandConverter,
public PPx::SpecificMenuCommandEnableDoer
と変えてください。
目的は、SpecificMenuCommandEnableDoer
・先ほど触れた裏方のCommandConveterを加える必要がある
・もともとあったCommandProcessDoerは、SpecificMenuCommandEnableDoerも
含めて、これらのコマンドやメニューハンドリングをするクラスの継承元として含まれているので、取ってしまった方がよい(ただし、残しておいてもコンパイル時に警告が出るだけで、動作的には支障はない)
「以上でOK」と言いたいところなのですが、あと少しだけを手を加えます。
MyApplication.cpで、MyApplicationのコンストラクタに2行加えて以下のようにします。
MyApplication::MyApplication()
{
EventTargetRef targetRef = GetSysEventTarget();
PPx::CommandConverter::Install(targetRef);
PPx::SpecificMenuCommandEnableDoer
先ほども言いました
PPx::CommandProcessDoer::Install(targetRef);
は、
PPx::SpecificMenuCommandEnableDoer
から間接的に呼ばれていることになるので、外します。
では、これで実行してみてください。先ほどの説明通りに、ほとんどのメニュー項目が無効になるとともに、アプリケーションメニューのAboutPPxForXcode01の下にPreferences…という項目が表れたはずです。もちろん有効になっています。これは、コード変更前にはなかった項目です。
今回の変更では、単にこの項目を「有効にせよ」と指示しただけなのですが、システムが環境設定項目が必要だと判断して、用意してくれたのだと思います。
書籍紹介 矢野りん著 デザインする技術
解説担当:高橋政明
デザインする技術
矢野りん
インプレスコミュニケーションズ ISBN978-4-8443-5858-8 2,000円+税
アプリケーションに限らずwebページなど「デザイン(視覚的美術的な意味でのデザイン)」は不可欠です。自分でデザインしなければならない場合はもちろん専門家の力を借りることができる人もデザインの素養は必要でしょう。
才能はどうすることもできないとしても、基礎的な技術を習得し改善されるならと本書を手にしました。
ものづくりの手がかり 「考」の技法
点と線で家が建つ 「図」の技法
身近だから知らない 「文字」の技法
情報の舞台装置 「面」の技法
目に見えることすべて 「色」の技法
の五つの章に分類された、見開きまたは1ページ完結の技法が載っています。それぞれカラーの図(実例)と簡潔な解説に加え、注、この技法の用途、デザインテイストが載っています。縦組の本文が新鮮でコラムで取り上げている話題も親しみやすく、全体も読みやすい本です。
『無難にまとまっているけど○○に似ている』とか『ユニークだけどダサイ』とか自分のデザインにはなかなか満足できないものです。本書でこんな悩みがすべて解決するわけではありませんが、原因の分析や対策一助にはなりそうです。
より広く、設計の意味でのデザインでも「考」の技法が参考になりそうです。寸法単位の解説や参考文献など「デザイン」関連情報の出発点として開発の現場に一冊どうぞ。
▼出版社のweb (詳しい目次あり、pdfで立ち読み可能)
http://www.mdn.co.jp/Books/Contents/5858M.htm
◇MOSAからのお知らせと編集後記は割愛します◇