viva Cocoa Objective-C 入門 第6章 デザインパターン
ホーム メール
 
目次 < 前ページ 次ページ >

デリゲートメソッド

デリゲートメソッド

 さてこれでトリガーメソッド(デリゲートメソッド)の実行は外部のControllerオブジェクトに任せることになりました。しかしこれらのメソッドの受付窓口は相変わらずText Viewにあります。なお、デリゲートメソッドのメソッド宣言は、Mac OS X 10.6 Snow Leopardから、それぞれのクラスに直接用意されるのではなく、それぞれのクラス名を冠した“クラス名Delegate”プロトコルというファイルに用意されることになりました。

 実際に“NSTextView Class Reference”と“NSTextViewDelegate Protocol Reference”を見てみましょう。

  1. Xcodeをアクティブにしてヘルプメニューからデベロッパドキュメントを選びます
  2. デベロッパドキュメントの“Search”フィールドに“NSTextView”と入力します

図 NSTextView Class Reference

 NSTextView Class Referenceは無事に表示されますが、このリファレンスを全部見ても“Delegate Method”と表記されたメソッドは見つかりません。

  Searchフィールドに“NSTextViewDelegate”と入力するか画面左上の“API”グループから“P”とマークの付いた“NSTextViewDelegate”を選んでください。

“P”はプロトコル、“C”はクラスを表しています

図 NSTextViewDelegate Protocol Reference

 現れるNSTextViewDelegate Protocol Referenceを下へスクロールすると“Tasks”項目に様々なデリゲートメソッドを確認することができます。しかし今回使いたいデリゲートメソッドは見つかりません。

 リファレンスページ冒頭の“Conforms to”グループの“NSTextDelegate”をクリックして、NSTextViewのひとつ上のスーパークラスである“NSText”のデリゲートプロトコルに移動します。

図 NSTextDelegate Protocol Reference

 今度はTasks項目の1番目に目指すデリゲートメソッドが見つかりました。

 “textDidChange:”デリゲートメソッドはテキストビューのテキストを変更するたびデリゲート先へ送信されるメッセージです。クリックして詳細ページへ移動します。正確なシグネチャは次のようになっています。

    - (void)textDidChange:(NSNotification *)aNotification

引数の“NSNotification”は「通知」という意味です。「通知」については後の章で説明しますが、ここでは、このシグネチャをこのまま使ってもらって大丈夫です。

リファレンスでメソッドなどを見つけた場合はそれをコピーしてコードファイルにペーストするほうが記述ミスもなく便利です。

■ コーディング

 Controller.mのawakeFromNibメソッドの次にtextDidChange:デリゲートメソッドを記述することにします。何度か言っていることですが、メソッドを記述する順番はプログラムに影響を与えません。しかし読みやすさのためには、順序立てて記述していくほうが良いでしょう。

Controller.m
- (void)textDidChange:(NSNotification *)aNotification { [model setString:[[textView string] copy]]; NSLog(@"%@", [model string]); }


 また、MyClipAppDelegate.mのapplicationDidFinishLaunching:デリゲートメソッドの実装コードは削除しておきましょう。次の例ではコメントアウトしていますが、実際には削除してください。

MyClipAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { //[[window firstResponder] insertText:@"Hello, World!"]; }

■ 実行

  1. Xcodeの実行メニューからコンソールを選びます
  2. コンソールウィンドウの「ビルドと実行」をクリックします。
  3. ビルドに成功してMyClipウィンドウが現れましたら、好きな文字を入力してください。
  4. 英数字の場合は入力するたびに、日本語の場合は変換を確定するたびに、テキストビューに入力されているすべての文字がコンソールウィンドウに表示されます。

■ コード説明

 ここでのコード説明は比較的重要な説明になります。ここまでは積極的にMac OS X 10.6の新機能ともいえる「選択権付きプロトコル」を使ったデリゲートの紹介をしてきました。しかしこの手法は2009年11月の時点では、まだまだ十分に整備されている状態とはいえないでしょう。ここではそのことについても説明します。

Controller.m
textDidChange:デリゲートメソッド
    [model setString:[[textView string] copy];
 このメッセージ式は次の3つのメッセージがネストされています。
    [textView string]
 このメッセージ式でテキストビューに表示されている文字列を取得します。
    [[textView string] copy]
 前述のメッセージ式で取得した文字列を“copy”メッセージで複製しています。なぜ複製する必要があるのかは後ほど説明します。
    [model setString:[[textView string] copy];
 modelインスタンス変数に登録したオブジェクト、つまりModelオブジェクトに前述までで得られた文字列を引数としてsetString:メッセージを送っています。これによってModelオブジェクトのstringインスタンス変数にテキストビューに表示されている文字列と同じ文字列を保持する文字列オブジェクトが格納されます。

MyClipAppDelegate.m
applicationDidFinishLaunching:デリゲートメソッド
    [[window firstResponder] insertText:@”Hello, World!”];
 この実装コードを削除しなければMyClip起動時に“Hello, World!”と表示されます。

 以上2つのデリゲートメソッドでは問題が出ることはありません。Xcode 3.1、3.2を問わず、同じコードが使えます。また、applicationShouldTerminateAfterLastWindowClosed:デリゲートメソッドも前章でコーディングしたままで問題ありません。問題は、これら2つのクラスのヘッダーファイルの@interface行にあります。この行には次のように強調表示された“プロトコルの採用”と呼ばれるコードが記述されています。


Controller.h
    @interface Controller : NSObject <NSTextViewDelegate> {
MyClipAppDelegate.h
    @interface MyClipAppDelegate : NSObject <NSApplicationDelegate> {
 Mac OS X 10.6ではこの2つのコードによってデリゲートが実現されていることになっていますが、実際にはこのコードを記述しなくても問題なく動作します。この2つのコードを削除するか次のようにコメントアウトして「ビルドと実行」をしてみてください。
    @interface Controller : NSObject /*<NSTextViewDelegate>*/ {
    @interface MyClipAppDelegate : NSObject /*<NSApplicationDelegate>*/ {

 Controller.mのawakeFromNibメソッドの中の[textView setDelegate:self];というコードに警告は出ますが、アプリケーションは正常に起動動作します。当然この場合はInterface BuilderでテキストビューのdelegateにControllerが接続されている必要があります。

 次にMac OS X 10.5対応でMyClipをビルドしてみましょう。次の手順に従ってMac OS X 10.5対応でビルドできるようにします。

  1. MyClipプロジェクトウィンドウの左ペインの“ターゲット”ディスクロージャを開き“MyClip”を選択し、情報ボタンをクリックします。
    図 MyClip - 情報
  2. 情報ウィンドウで“ビルド”タブを選び、“ベースSDK”から“Mac OS X 10.5”を選びます。
    図 情報 - ベースSDK
  3. 情報ウィンドウを閉じて、MyClipプロジェクトウィンドウで“ターゲット”を“Mac OS X 10.5”にします。
    図 ターゲット - Mac OS X 10.5

 これで、10.5対応でビルドする準備が整いました。「ビルドと実行」をクリックしてください。警告もエラーも出ずに正常にビルドでき、MyClipも正常動作します。

 次にController.hとMyClipAppDelegate.hの@interface行を次のように書き換えて“プロトコルの採用”を有効にして、「ビルドと実行」をします。おそらく“失敗!2”でビルドできないことだろうと思います。

Controller.h
    @interface Controller : NSObject <NSTextViewDelegate> {
MyClaipAppDelegate.h
    @interface MyClipAppDelegate : NSObject <NSApplicationDelegate> {

図 MyClipプロジェクトウィンドウ - 失敗


 では次に“ターゲット”を“Mac OS X 10.6”に変更して「ビルドと実行」をします。今度は警告も出ずに“問題なく完了しました”と表示されてMyClipは正常にビルドされ、正常に動作します。

図 MyClipプロジェクトウィンドウ - 10.6


■ まとめ

 ここまでの結果をまとめると次のようになります。

【注釈】

 この節の構成は、どうも変です。おそらく私が半分眠りながら書いたか、正規の原稿と入れ違いになったような感じです。しかし内容は間違いないので、重複説明になる箇所も多いとは思いますが、ここまでを「第6章 第3節 第1項 デリゲートメソッド」とし、ここから先を「第6章 第3節 第2項 デリゲートメソッドの実装」という項目名で説明を続けることにいたします。

デリゲートメソッドの実装

[注意]
この項は、Xcode 3.1 (Mac OS X 10.5 Leopard)の画面キャプチャを使って説明しています。

 この項ではNSTextクラスで定義されているtextDidChangeデリゲートメソッドを例として、もう少し実際的な説明をしていきたいと思います。なお、この項での説明はXcode 3.1 (Mac OS X 10.5 Leopard) での説明になりますが、Xcode 3.2 (Mac OS X 10.6 Snow Leopard) でも十分に読みこなせる内容になっていると思います。


■ リファレンス

 Xcodeを起動して「ヘルプ」メニューから「製品ドキュメント」を選んでください(Xcode 3.2の場合は「ヘルプ」メニューから「デベロッパドキュメント」を選んでください)。左ペインの「ドキュメントセット」から1番上のApple Mac OS X 10.5を選び右上の検索フィールドにNSTextViewと入力するとNSTextViewクラスリファレンスが現れます(Xcode 3.2 の場合は、直接、検索フィールドにNSTextViewと入力してください)。

図 NSTextViewクラスリファレンス 1

 このクラスリファレンスを下にスクロールしていくと「delegate Method」と追記のあるメソッドがいくつか見つかると思います。このメソッドがデリゲートメソッドです。

図 NSTextViewクラスリファレンス 2

 今回はNSTextViewのスーパークラスのNSTextで宣言されているtextDidChange:デリゲートメソッドを使ってみようと思います。クラスリファレンスの先頭に戻って Inherits from グループからNSTextをクリックするか検索フィールドにNSTextと入力して下さい。NSTextクラスリファレンスが現れます。下のほうにスクロールして-textDidChange:デリゲートメソッドを探してみてください。

図 NSTextクラスリファレンス 1

-textDidChange:デリゲートメソッドが見つかりましたらクリックして詳細を見てみましょう。

図 NSTextクラスリファレンス 2

    - (void)textDidChange:(NSNotification *)aNotification

 というシグネチャになっています。引数のaNotificationは「通知」という意味になりますが、ここでは変更せずにこのまま使って頂いて大丈夫です。「通知」については後の章で簡単な使用例を示します。そして「通知」の詳細は実践編で説明しております。

残念ながらは当コーナーでは「実践編」は掲載していません。ご了承ください。

 クラスリファレンスでメソッドなどを見つけた場合はそれをコピーしてコードファイルにペーストするほうが記述ミスもなく便利です。リファレンスで上記シグネチャ部分を選択してcommand + Cでコピーしてください。
※クラスリファレンスではなぜかコンテクストメニューに「コピー」項目が現れません。

 次にController.mを開きます。前にも言いましたがメソッドは@implementationと@endのあいだにコーディングされていれば問題ありません。メソッド同士の記述順序が機能に影響することはありません。ただ人が見た場合にある程度の順序化やグループ化ができているほうが読みやすいことは確かです。今回はawakeFromNibの次にこのメソッドを記述することにいたしました。なおContoroller.hにtextDidChangeのメソッド宣言を記述する必要はありません。
また、逆に記述したとしても問題が起こることもありません。

textDidChangeメソッド
- (void)textDidChange:(NSNotification *)aNotification { [model setString:[[textView string] copy]]; }

■ コード説明

	[model setString:

 ModelオブジェクトにsetStringメッセージを送っています。setStringメソッドはModelオブジェクトのstrigインスタンス変数の値を設定するメソッドでしたね。もしお忘れのようでしたらModel.mもぜひもう一度目をとおしておいてください。

    [textView string]

 setString:メソッドの引数の値に、つまりstringインスタンス変数に設定する値を得るためにtextViewにstringメッセージを送っています。stringメソッドはText Viewから(正確にはNSTextから)そこに書かれているプレーンな(属性のない)文字列を取得するメソッドです。

    [[textView string] copy]

 通常であれば[textView string]だけで大丈夫なのですが、NSTextViewの場合はサスペンド(コンピュータ用語としては「その時々の状態」という意味になります)を残さない仕様になっています。つまり一旦stringで取得した文字列もNSTextViewが変更されればそれにともなってstringインスタン変数に格納された値も変更してしまうということです。Cocoaフレームワークではこのような仕様になっているクラスは珍しくありません。そこでstringメソッドで取得した値をcopyしてNSTextViewとは切り離した新しいオブジェクトを生成しています。そしてそのコピーした値をModelオブジェクトのsetStringメソッドに引数として渡しています。

 Text Viewの内容が変化すればそれに伴ってModelのインスタン変数の値も変更されるのであれば便利ではないかという意見もあると思います。しかしText Viewはひとつだけです。それに対してその時々のText Viewの値を格納するためにModelオブジェクトのインスタン変数が複数存在する場合も十分に考えられます。この場合は前述のような対処法をとっておかなければすべてインスタン変数が同じ値を指し示すことになります。

 以上で無事にText Viewの値がtextDidChangeをトリガーとして、つまりText Viewの文字が変更されるたびにModelオブジェクトのstringインスタン変数の値が更新されるようになりました。とは言ってもModelオブジェクトの値を確認するすべがありません。そこでContoroller.mのtextDidChange:メソッドにもう少し仕掛けを付け加えることにいたします。Contoroller.mのtextDidChange:メソッドに次のように強調文字になっているコードを付け足してください。

Controller.m / textDidChange:メソッド
- (void)textDidChange:(NSNotification *)aNotification { [model setString:[[textView string] copy]]; NSLog(@"%@", [model string]); }

 ModelオブジェクトにText Viewの文字列が保持されたかどうか確認するためにNSLogというObjective-Cの関数を使ってコンソールにstringインスタンス変数の値を表示するようにいたしました。NSLogはC言語のprintfとほぼ同じ働きをします。そしてprintfと同じ変換修飾子や変換指定子が使えますが、%@という変換指定子が追加されています。%@はそれに該当する引数にオブジェクトを指定します。そして表示される内容はそのオブジェクトのdescriptionというメソッドで設定されている値が表示されます。その値は多くの場合、そのオブジェクトが表している値自体になっています。つまりここではstringインスタンス変数に格納されている文字列が表示されることになります。なおNSLogでは表示する文字列自体がオブジェクトである必要があります。ダブルクォーテーションマークの前に@マークが入っていますね。つまり文字列がNSStringのオブジェクトであることを表しています。さらにNSLogではコンソールに表示される文字列の前に時刻とプログラム名などが表示されます。また\nを記述しなくてもNSLogの呼び出しごとにその文末は改行されることになっています。もしそこに\nを追加すれば2回改行されることになります。

 ではここまでの作業をすべて保存してXcodeの「実行」メニューから「コンソール」を選んでコンソールウィンドウを表示させてください。そして「ビルドして進行」をクリックしてコンパイルします。メインウィンドウが起動しましたら文字を入力してみください。入力のたびごとに入力した文字列が次々とコンソールウィンドウに表示されることだろうと思います。また英数字の直接入力時と日本語の入力時とではコンソールに表示されるタイミングが違います。そのことも合わせて確認してみてください。

図 MyClip動作確認 - デリゲート


 おめでとうございます。これであなたはObjective-Cのデザインパターンのうちデリゲートをマスターされました。お疲れ様でした。なお、今作成しているMyClipもメインウィンドウをひとつだけ持つシングルトンアプリケーションというデザインパターンのひとつになっています。



目次 < 前ページ 次ページ >


Copyright 2006 - 2010 viva Cocoa. All Rights Reserved.