第5章 アプリケーションの初期化 | ||
ホーム | メール | |
目次 | < 前ページ 次ページ > |
この章からは、前章で作成したMyClipという、まだ外枠だけのアプリケーションに色々な機能を搭載しながらObjective-Cの言語仕様の学習を進めていきたいと思います。ここでは「搭載」という言葉を使いましたが、プログラミング用語としては「実装」と呼ぶほうが正しいでしょう。しかし「メソッドの実装」との混同を避けるために敢えて搭載と呼ぶことにしました。
さて、本書の冒頭でも述べたとおりObjective-Cはいわゆるデザインパターンというものを密接に組み合わせてプログラミングしていくように設計されています。この章では、これらのデザインパターンの中からまずデリゲートの説明をします。そしてこれからの数章にわたり、その他のデザインパターンについても説明をしますが、これらのObjective-Cのデザインパターンに共通して言えることは、「いかにして継承せずにオブジェクトにメソッドを追加するか」ということに集約されると思います。
このように書くと、難しいことのように思われるかもしれませんが、実際にやってみれば、それほど難しいことではありません。ここで肝心なのは慣れだけでしょう。これからの数章では、Objective-Cを使ってソフトウェアを構築していく上で、他のOOPと考え方や対処法が違う点をどんどん取り上げて、少しでも早く、Objective-Cによるプログラミングに慣れてもらうことを目標としています。
なお、デリゲートとはデリゲートメソッドと呼ばれるものを扱う仕組みの総称です。ようするにデリゲートとはメソッドの一種です。このように聞くとそれほど難しいことではないと思えることでしょう。
デリゲートの説明する前にひとつ押さえておきたいことがあります。それは「アプリケーション独自の初期化をいつ、どこで行うか」ということです。このように言うと難しそうに聞こえるかもしれませんが、簡単な例で言うと、MyClipが起動した時にテキストビューに“Hello, World”と表示するように初期設定したい。などというようなことです。このことは、一見大事そうには見えませんが、実際にプログラミングを始めてみると、真っ先にぶつかる問題になることでしょう。
Objective-Cでは、この「アプリケーション独自の初期化をいつ、どこで行うか」ということについて答えが2つ用意されています。その中ひとつが“awakeFromNib”というメッセージ(メソッド)を使う方法です。そしてもうひとつが“NSApplication”というオブジェクトのデリゲート(メソッド)を使う方法です。前者はMac OS XのGUIを持つプログラムつまりアプリケーションで従来から一般的に使われていた方法です。
後者もまた、NSApplicationという名前からも連想できるとおり、やはりGUIを持つプログラム(アプリケーション)にとって有効な手段です。そしてawakeFromNib同様Mac OS X 10.0からサポートされている方法です。しかし実際にはあまり使われていませんでした。awakeFromNibのほうがはるかに有名だったと言えるでしょう。ところがMac OS X 10.6 Snow Leopard 、つまりXcode 3.2.1からはCocoa Applicationの新規プロジェクトに、この「NSApplicationのデリゲートメソッドを受け取るクラス」(ここは少し難しい表現ですが、とりあえず読み進めてください。詳しい説明は後ほどします)がデフォルトで用意されるようになりました。このことは「NSApplicationのデリゲートメソッドをもっと利用するようにしましよう」というAppleからの指針と受けとめて良いでしょう。
では、この2つの方法を個別にみていくことにしましょう。
MyClipの起動時にテキストビューに「Hello, World!」と表示させるには、Interface Builder上でText Viewに直接“Hello, World!”と書き込んでおく方法が一番簡単な方法です。しかしここでは説明の都合上、この方法は使わないことにしています。
【Xcode 3.1の場合】
この「アプリケーション独自の初期化」を使って、MyClip起動時にテキストビューに表示される英文をなくして、白紙のテキストビューが表示されるようにします。
前章ではControllerクラスのinitメソッドのなかで、Modelクラスのインスタンス化と初期化をおこないました。では、そのControllerクラスはいつインスタンス化され初期化されるのでしょうか。Controllerクラスはアプリケーションが起動してxibファイルが読み込まれる時にインスタンス化され初期化されます。前章でControllerオブジェクトをこのMainMenu.xibウィンドウに登録しましたが、このようにxibファイルには目に見えるビューオブジェクトだけではなく、Controllerオブジェクトのように目に見えないオブジェクトも登録することができます。
図 Interface Builder - MainMenu.xibウィンドウ
このxibファイルはアプリケーションが起動していない時には“アーカイブ”(バイト列データ化)された状態で保存されています。アーカイブについては後の章で詳しく説明しますが、いわば冷凍保存、もしくは圧縮保存されたような状態になっています。そしてアプリケーションが起動するときにこのバイト列データが解凍され元のかたちに復元されます。xibファイルに含まれているオブジェクトもこの時に次々とインスタンス化され初期化されます。Controllerクラスもこの時にインスタンス化され初期化されることになります。そしてControllerオブジェクトが初期化される時にModelクラスがインスタンス化され初期化されるわけです。
しかしアウトレットの接続は、xibファイルに登録されているすべてのオブジェクトの復元が終了してからおこなわれることになっています。ここで「アウトレットの接続」と限定した言い方をしましたが、アウトレットとはInterface Builderの中だけで、つまりxibファイルだけで通用する呼び方です。実際にControllerオブジェクトのinitメソッドでは“model”インスタンス変数にModelオブジェクトをインスタンス化して代入しています。こちらはxibファイルのすべての復元が終わらないうちに繋げることができるわけです。
話しを戻します。Controllerオブジェクトはテキストビューを参照する“textView”というアウトレットを持っています。ここでは、そのテキストビューに文字列を初期設定したいわけですが、Controllerオブジェクトのinitメソッドが実行されている時にはまだこのtextViewアウトレットは繋がっていません。なぜなら、この時点ではText Viewはまだ復元されていない可能性があるので、前述のようにxibファイルに登録されているすべてのオブジェクトが復元されてからアウトレットの接続が行われる仕組みになっているからです。しかし、このままではテキストビューの初期化が行いことになります。
この問題に対処するには2つの方法が考えられます。ひとつは、NSTextViewクラスのインスタンスを直接使用するのではなく、NSTextViewクラスのサブクラスを作成し、そのサブクラスのinitメソッドを上書きして、自身のinitメソッドの中で表示される文字列を初期設定するという方法です。そしてもうひとつがawakeFromNibメッセージの一斉送信という方法です。この2つのうちObjective-Cでは後者を採用しています。多くのOOPでは継承を多用する形になっています。しかしObjective-Cでは、本章の冒頭でも述べたようにクラスの継承という手段をできるだけ使わずにプログラミングできるように設計されています。
【注意】
NSTextViewはCocoaフレームワークに最初から用意されているクラスです。サブクラス化せずに直接initメソッドを変更することはできません。
awakeFromNibメソッドはすべてのクラスのルートクラスであるNSObjectで定義されています。したがってすべてのオブジェクトがこのメッセージを受け取ることができますが、実際にこのメッセージが送信されるオブジェクトはxibファイルに登録されているオブジェクトだけです。言い方を換えれば、awakeFromNibメッセージを受け取ってawakeFromNibメソッドを実行することになるのはxibファイルに登録されているオブジェクトだけとなります。
このawakeFromNibメソッドのようにスーパークラスやルートクラスから継承したメソッドはヘッダファイルにプロトタイプ(メソッド宣言)が記述されていなくても実装ファイルでいきなり実装をすることができます。ここではあえて上書きと言わず実装と言う事にしました。なぜなら、awakeFromNibは、はじめから上書きされることを目的とした実装のないバーチャルメソッドと呼ばれるものだからです。
[バーチャルメソッド]
前述のようにサブクラスで実装されることを前提とした定義(外枠)だけのメソッド。バーチャルメソッドの定義方法は、それぞれのOOPによって違います。
xibファイルが再構築され、xibファイルに登録されているすべてのオブジェクトの復元が終了した時。xibファイルに登録されているすべてのオブジェクトにこのawakeFromNibメッセージが送信されます。そしてxibファイルに登録されているすべてのオブジェクトはこのメッセージを受け取ることができるわけです。
では実際にコーディングして試してみましょう。MyClipが起動した時にテキストビューに”Hello, World!“と表示されるようにします。なおこれはテストとしておこなうだけです。MyClipはメモ・ソフトです。起動時に真っ白いウィンドウが表示されて当たり前です。テストが終わり次第、元に戻したいと思います。
では、コーディングをといきたいところですが、その前にまだ悩むことがあります。それは、どのファイル(オブジェクト)にawakeFromNibメソッドを実装すれば良いのかということです。しかし実際にはxibファイルの中で私たちがコードを変更できるオブジェクトはControllerとMyClipAppDelegateしかありません。そしてコントローラはビューとモデルの更新に責任を持つという大事な役割を担っています。この項では、この従来のMVCの考えに基づいてControllerにawakeFromNibを実装します。また、前章でControllerにテキストビューへのアウトレットも設定済みです。
実際にはフレームワークに用意されている既存のクラスにも“カテゴリ”という方法でメソッドを追加することができます。カテゴリについては章を改めて説明します。また、awakeFromNibメッセージはxibファイルに登録されている他のオブジェクトも受信します。しかし必ずそれに応えなければならないということはありません。メッセージを受け取っても無視する、というのはObjective-Cではよく使われる常套手段のひとつです。
【Xcode 3.1の場合】
MyClipAppDelegateというクラスは存在しません。後ほど自作することにします。
コーディング手順
MyClipプロジェクトを立ち上げてClassesフォルダを選択してController.mを開いてください。awakeFromNibメソッドは@implementationと@endのあいだであればどこに記述しても構いません。メソッドを定義する順番はプログラムに影響を与えません。そのことはヘッダファイルでのメソッド宣言が実質はプロトタイプであることからも分かっていただけることでしょう。しかしここでは、まずすべてのオブジェクトがinitされてからawakeFromNibが呼び出されるという順序に従いinitメソッドの次に記述することにします。そのほうが流れ的に分かりやすいでしょう。では、initメソッドの次にawakeFromNibメソッドを次のようにコーディングしてください。
- (void)awakeFromNib { [textView setString:@"Hello, World!"]; }
コーディングが終わりましたら「ビルドと実行」をクリックしてください。MyClipが起動するとウィンドウに「Hello, World!」と表示されます。確認が終わりましたら、MyClipを終了してawakeFromNibメソッドの実装を次のように変更しておいてください。
- (void)awakeFromNib { ; }
これでawakeFromNibは何もしないメソッドになります。再度「ビルドと実行」をクリックしてMyClipを起動してください。元のように何も書かれていないウィンドウが表示されます。なおawakeFromNibメソッドは、今後MyClipアプリケーションにおいて活躍することになりますのでメソッド定義はこのまま残しておきます。
【Xcode 3.1の場合】
テストが終わりましたら次のようにコーディングしておきましょう。
- (void)awakeFromNib
{
[textView setString:@""];
}
これでMyClipが起動した時に真っ白いテキストビューが表示されるようになります。今後、Xcode 3.1の場合には、本書でのawakeFromNibの一行目には、サンプルコードに何も書かれていない場合でも常にこのメッセージ式を記述するようにしてください。
なお空文字を生成するには、次のメッセージも有効です。
[textView setString:[NSString string]];
このstringメッセージ(メソッド)は、NSStringクラスのクラスメソッドで、空文字を表すNSString型の一時オブジェクトを生成します。なお一時オブジェクトについては後ほど説明します。
テキストビューを参照しているインスタン変数textViewにsetString:メッセージを送っています。setString:はNSTextViewのスーパークラスであるNSTextで定義されているメソッドです。setStringの正式なシグネチャは次のとおりになります。
- (void)setString:(NSString *)aString
引数としてNSString型のオブジェクトを必要とします。今回は @”Hello, World!” を引数にしています。この@”○○○”という記述方法は3章でも説明しましたがNSString型のオブジェクト定数を生成する記述方法です。つまり@マークに続く ” (ダブルクォーテーションマーク)で囲まれた文字列(1文字の場合や空文字もあります)をアプリケーション終了時まで保持し続けるNSString型のオブジェクトを生成します。
ここで少し横道にそれますがメソッドのシグネチャについて復習しておきたいと思います。例えば矩形(四角形)の横と縦のサイズを設定するC言語の関数のシグネチャは次のようになります。
void setRectangleSize(int widthSize, int heightSize)
これをObjective-Cのメソッドのシグネチャに書き換えれば次のようになります。
- (void) setRectangleWidth:(int)widthSize height:(int)heightSize
まず先頭にクラスメソッドであるかインスタンスメソッドであるかを表す + もしくは - を付けます。次に戻り値や引数の型は( )で囲みます。引数の前はコロン“:”で区切ります。複数の引数がある場合はそれぞれの引数にラベルを付けることが出来ます。前述のメソッドでは次のように強調箇所の“height”がラベルになります。
setRectangleWidth:(int)widthSize height:(int)heightSize
このラベルを付けることによってその引数が何の値かが分かりやすくなりますがメソッド名が長くなるというデメリットもあります。このラベルを付けない場合と付けた場合では別のメソッドとして扱われます。つまり次の2つのメソッドは違うメソッドとして区別されます。
- (void) setRectangleWidth:(int)widthSize:(int)heightSize - (void) setRectangleWidth:(int)widthSize height:(int)heightSize
なお引数はラベルの有無に関わらずいつくでも設定することができることはCやその他の言語と同じです。
NSApplicationDelegateは本章の最初に述べたとおりMac OS X 10.6で新しく採用されたプロトコルです。プロトコルの説明は後ほどするとして、ここでは便宜的にMac OS X 10.6から採用された新しいクラスと考えてもらって差し支えありません。そしてここからが実質的にデリゲートの説明の始まりとなります。
MyClipプロジェクトウィンドウの左ペインでClassesを選択すると右ペインに3つのクラスのヘッダファイルと定義ファイルが表示されます。この3つはプログラマが自由にコーディングできるクラスになっています。この中で“MyClipAppDelegate”は新規プロジェクトを作成した時に自動で作成されるクラスです。このクラスは必ず「プロジェクト名AppDelegate」という名前になります。そしてこれがNSApplicationDelegateになります。詳しい説明は後にして、早速このクラスを使ってテキストビューの初期化をおこなってみましょう。MyClipAppDelegate.mを開くと次のようにメソッドがひとつ定義されています。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application }
メソッド名の中で使われている“Launching”という単語はアプリケーションの起動のことを表します。日本語ではアプリケーションの場合もOSの場合も同じ「起動」という言葉で表現しますが、英語では区別されています。また“Did”や“Will”や“Should”などの助動詞が使われることもデリゲートメソッドの特徴です。結果としてこのメソッドは「アプリケーションの起動が終了した直後」に呼び出されることが、メソッド名から容易に連想できることでしょう。では「アプリケーションの起動が終了した時」とはどういう状態でしょうか。それは当然、xibファイルの復元も終了してMyClipを使い始める準備が整った時のことを表します。それであれば、awakeFromNibメソッドの代わりに、このメソッドで、そのアプリケーション独自の初期化をおこなえば良いことになります。事実、メソッドシグネチャに続くコメント部には次のように書かれています。
// Insert code here to initialize your application
ようするにAppleは、Mac OS X 10.6(Xcode 3.2.1)で明確に「アプリケーション独自の初期化コードはここに書け」と指針を出したことになります。
ここでNSApplicationオブジェクトについて説明しておきます。NSApplicationオブジェクトはアプリケーション(GUIを持つソフトウェア)において必ずひとつだけ作成されます。このNSApplicationはアプリケーション全体を管理するオブジェクトですが、その多くの役割の中には次の事も含まれています。
そしてこれらの事が行われる時、NSApplicationはデリゲートメソッドというものを送信します。デリゲートメソッドは宣言だけされている定義(実装)のないメソッドです。 しかし定義がされていなければメッセージを送信しても何もおこりません。そこでデリゲートメソッドの定義は、NSApplicationの“delegate”インスタンス変数に登録されたオブジェクトにコーディングするという決まりになっています。このことを第3章では「外注に出す」と言っていたのです。そしてさらに重要なことですが、delegate先にコーディングするメソッド定義は必要とするメソッドだけで良いことになっています。NSApplicationには十数個のデリゲートメソッドが宣言されていますが、その中から必要なものだけをdelegate先のオブジェクトにコーディングすれば良いのです。
なお、最後にもうひとつ大事なことですが、Cocoa Applicationにおいてxibファイルのオーナー(所有者、管理者)は、このNSApplicationになります。ModelオブジェクトはControllerオブジェクトによってインスタンス化が命じられて、Controllerオブジェクトはxibファイルによってインスタンス化が命じられることはすでに言いました。ではそのxibファイルの復元は誰が命じているのかと言えば、このNSApplicationが命じているという関係になります。このことからもアプリケーション独自の初期化はNSApplicationによって(NSApplicationのデリゲートメソッドによって)行われるほうが理にかなっていることになります。
このようなデリゲートメソッドはNSApplicationだけではなく、Cocoaフレームワークの多くのクラスが持っています。
このデリゲートメソッドをクラスに設定する方法はそれほど難しいことではありません。しかしここでは敢えて詳しい説明をしないことにします。なぜなら多くのプログラマにとって知りたい事は、ある機能の使い方であって、その機能がいかにして実現されているかということは二の次だろうと思うからです。そして実際に必要性を感じていないことを説明することは、おそらく学習の邪魔になる可能性もあることだろうと思います。そこでこのことについての詳しい説明は補足資料B「プロトコルとカテゴリ」の中でおこなうことにします。
しかし、次のことは予備知識として知っておいてほうが良いでしょう。ただし話しは難しいです。おおよその感じだけでも掴んでもらえれば結構です。
Objective-Cには、カテゴリと呼ばれるCocoaフレームワークなどで定義されている既存のクラスにメソッドを追加する方法があります。本来は自作クラスではない既存のクラスにサブクラス化せずにメソッドを追加することはできません。おそらくこのカテゴリはObjective-Cの柔軟性を示す最たる例と言えるでしょう。
カテゴリは既存のクラスにメソッドの宣言と定義を追加するコードファイルを記述することによって実現されます。デリゲートメソッドはすべてのクラスのルートクラスであるNSObjectへ、このカテゴリという方法を使ってメソッドの宣言だけを追加することによって実現されています。そして、このことを非形式プロトコル(informal protocol)と呼びます。
一方、プロトコルも、クラスにメソッドを追加する方法ですが、既存のクラスに新たにプロトコルメソッドを追加することはできません。あくまでも新規の(自作の)クラスにすでに用意されているプロトコルメソッドの宣言と定義を組み入れる方法です。ただしCocoaフレームワークの既存のクラスも、その設計段階でこのプロコトルを組み入れています。
このプロトコルという方法のメリットは、そのプロトコルを組み入れた(正式には、このことを「プロコトルの採用」と呼びます)クラスがどういう機能を持っているか一目で分かるという点があります。そしてこのプロトコルのことを形式プロトコル(formal protocol)と呼んで、前述の非形式プロトコルと区別します。実際に形式プロトコルと非形式プロトコルは同じプロトコルという名前が付いていますがまったく違うものです。
さてここからが問題になりますが、従来、形式プロトコルは、それを採用したクラスではプロトコルで宣言されているすべてのメソッドを定義しなければなりませんでした。しかし2007年9月に正式リリースされたObjective-C 2.0では、このプロトコルメソッドの取捨選択ができるようになりました。つまり必要なものだけを定義できるように変わったのです。
このことはObjective-C 2.0から採用されたガベージコレクションなどの他の機能に比べると地味だったせいか、あまり積極的に喧伝されることはありませんでした。しかしObjective-C 2.0が発表されてから2年。Snow Leopardの登場とともに表舞台に出てきた感じがあります。
NSApplicationDelegateプロトコルは、このメソッドの取捨選択ができるプロトコルとして定義されています。そして結果として、必要なものだけを定義すれば良いデリゲートと同じ働きをすることになっています。そのことはNSApplicationDelegateという名前からも分かるとおりです。さらにリファレンスを読むとこのプロトコルのことをinformal protocol(非形式プロトコル)と説明されています。つまりここにきてデリゲートとプロトコルの垣根がなくなってきているのです。Objective-Cは地味ではありますが、さらに柔軟性を増して発展し続けています。
難しい話しが続きましたが、実際にMyClipAppDelegate(NSApplicationDelegate)を使ってみましょう。MyClipプロジェクトでClassesフォルダを選択して右ペインの中からMyClipAppDelegate.mを開いてください。強調箇所が追加・変更するコードになります。コーディングが終わりましたら「ビルドと実行」で結果を確かめてみてください。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[window firstResponder] insertText:@"Hello, World!"]; }
さきほどと同じように「Hello, World!」と表示されただろうと思います。しかし記述したコードはだいぶ違います。仮にこのMyClipAppDelegateにテキストビューへのアウトレットを設定すれば、ControllerオブジェクトのawakeFromNibと同じように次のコードを記述することができます。
[textView setString:@”Hello, World!”];
そして実際にこのコードを使用したほうが良いでしょう。なぜなら“textView”というアウトレットは、いついかなる場合もMyClipウィンドウのテキストビューを参照するように静的に固定されるからです。一方ここで使っている“firstResponder”というメッセージはいつも同じオブジェクトを表すとは限っていません(ただしMyClipにおいては常にテキストビューを指し示すことになりますが)。
しかしここでは敢えてfirstResponderを使ってみました。この機会にビューにおける“レスポンダチェーン”についても説明しておこうと思ったからです。
なお、MyClipAppDelegateにはデフォルトでNSWindow型の“window”というインスタンス変数が宣言されています。このwindowインスタンス変数はアウトレット指定されていませんが、続く“@property”コンパイラディレクティブでテキストビューに接続されます。@propertyコンパイラディレクティブについては後の章であらためて説明しますが、MyClipAppDelegateの接続関係はInterface Builderで確認しておいたほうが良いでしょう。
次項ではレスポンダチェーンについて説明します。レスポンダチェーンは、直接デリゲートには関係していませんが知っておいて損はないことだと思います。しかしその前にいつものとおり「Xcode 3.1の場合には」でNSApplicationのデリゲートメソッドを受け取るためのクラスをMyClipプロジェクトに追加します。このことは一見Xcode 3.2には関係のないことのように思えますが、MyClipAppDelegateの実質的な実体であるNSApplicationDelegateプロトコルは残念ながらMac OS X 10.6以上でないと使えません。したがってXcode 3.2においてもMac OS X 10.5に対応したアプリケーションを開発する場合には、「Xcode 3.1の場合には」で紹介している手法を採用することになりますので、ぜひ目を通されることをおすすめします。
【Xcode 3.1の場合】
「プロジェクト名AppDelegate」クラスがプロジェクトに自動で作成されることはありません。そこで同等の機能を持つクラスを作成することにします。
- (void)awakeFromNib
{
[textView setString:@""];
}
#import <Cocoa/Cocoa.h>
@interface MyClipAppDelegate : NSObject {
IBOutlet NSWindow *window;
}
@end
#import "MyClipAppDelegate.h"
@implementation MyClipAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[window firstResponder] insertText:@"Hello, World!"];
}
@end
無事にテキストビューに“Hello, World!”と表示されたことだろうと思います。なお、awakeFromNibで一度テキストビューを空文字で初期化し、再度NSApplicationのデリゲートメソッドで“Hello, World!”と設定していることに違和感を覚えるかもしれません。しかし、awakeFromNibはxibファイルの復元がすべて終わった後で送信されます。そしてapplicationDidFinishLaunchingはさらにその後でメッセージ送信されます。したがって順序としてまったく問題ありません。また“firstResponder”の“insertText:”メソッドはその時に入力カーソルがある場所に文字列を挿入するメソッドです。もしXcode 3.1でテキストビューにデフォルトで表示される英文を消していない場合は、英文の前に“Hello, World!”と挿入されるだけになります。
目次 | < 前ページ 次ページ > |