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

ターゲットアクションの接続

Interface Builderによるターゲットアクションの接続

 この項では引き続きInterface Builderでのターゲットアクションの接続を行います。

■ ターゲットアクションの接続

 まずPush Buttonを選択してください。それから右クリックしながらMainMenu.xibウィンドウのControllerまでドラッグして接続線を引きます。Controllerが強調表示されましたらマウスを離します。

 ビューパーツのなかには右クリックであっても、いきなりドラッグをはじめるとそのビューパーツそのものが動いてしまうものがあります。Push Buttonもそのうちのひとつです。まず選択してから右クリックでドラッグするとButton自体が動くことはありません。

図 ターゲットアクションの接続 1

 マウスを離すと接続先の一覧が表示されます。今回はReceived Actionsグループにひとつだけさきほど宣言・定義したindicate:メソッドが表示されます。このIndicate:メソッドをクリックしてください。これでボタンのターゲットアクションが接続されました。

図 ターゲットアクションの接続 2

 これでターゲットアクションの接続作業は完了しました。Interface Builderでの作業を保存してXcodeへ戻ります。Interface Builderは終了していただいても結構です。


■ 実行

 Xcodeの「実行」メニューから「コンソール」を選んでコンソールウィンドウを表示します。

 コンソールウィンドウが表示されましたら「ビルドして進行」をクリックしてMyClipアプリケーションをコンパイルしてください。無事にコンパイルが終わってメインウィンドウが現れましたら文字列を入力してみましょう。そしてModelオブジェクトに保持されたデータが見たいときには「Indicate 」ボタンをクリックします。するとコンソール画面にModelオブジェクトに保持されている文字列が表示されます。

図 MyClip実行画面 - ターゲットアクション


■ ターゲットアクションの説明

 この節の冒頭での簡単な説明と繰り返しになっている部分もありますが「図ターゲットアクションの接続」でボタンをControllerオブジェクトのindicateメソッドに接続しました。このことは見かけのうえではデリゲートの接続と似ています。しかしデリゲートの接続で行われることはdelegateインスタンス変数にデリゲートメソッドを実装するオブジェクトを接続するという、作業したことと同じようにひとつの接続が行われるだけです。これに対してターゲットアクションの場合はInterface Builderでの作業では確かに一度の接続しか行っていませんが、実際にはtargetというインスタンス変数とactionというインスタンス変数の2つに接続が行われています。targetにはボタンをクリックした場合に実行されるメソッドが実装されているオブジェクトが接続されます。actionには実行されるメソッドのselectorが設定されます。なお、このtargetとactionというインスタンス変数はNSButtonで宣言されているのではなく冒頭でも説明しましたとおりスーパークラスのNSControlで宣言されています。また、このようにユーザからの操作を受け付けるオブジェクトをコントローラクラスと呼びます。コントローラクラスにはNSButtonのほかにNSSliderやNSTextFieldなどがあります。

 もし、あなたがOOPが初めてであればこの話しを聞いても何も思わないかもしれません。しかし多くのOOPではデリゲートのときにもお話ししたように何かのトリガーメソッドが必要なクラスにはバーチャルメソッド(実際の実装は行わずに、最初からサブクラスで実装されることを目的としているメソッド)を定義しておいて実際にそのクラスを使う場合にはそのクラスのサブクラスを作成してバーチャルメソッドの実装を行ってから使うことになります。
 プッシュボタンを例にしてお話しをすれば、ボタンクラスにactionというバーチャルメソッドを定義しておきます。そしてボタンオブジェクトを使いたい場合は、このボタンクラスを継承してサブクラスを作りactionメソッドを実装します。つまりアプリケーションにボタンが複数ある場合はそのひとつひとつが別のクラスということになります。
 これに対してObjective-Cではアプリケーションにいくつボタンがあってもすべて同じボタンクラスのインスタンスです。そしてアクションはメソッドとしてではなくインスタンス変数として宣言して、外部のオブジェクトに実装しているメソッドを登録するようになっています。

 実際にactionメソッドを継承を行ってそのオブジェクト自身に実装する場合と継承を行わずに外部オブジェクトに実装する場合のどちらがコーディングしやすいでしょうか。ボタン作成をコードで書かなければならないのであれば圧倒的にObjective-Cのほうが有利です。しかしこれは比較にはなりません。Xcodeと同じようにGUIを専用のツールで作る環境のある言語、ようるするにObjective-Cと同じ条件にした場合を考えるとボタンオブジェクト自身にそのactionを実装するほうが理解はしやすいです。しかしプログラミング言語のもうひとつの目標である可読性という点ではMVCにしたがって各ボタンのactionメソッドをコントローラオブジェクトに集約するかたちになっているObjective-Cのほうがコーディングに慣れてくれば読みやすくなるというかコードの流れが分かりにくくなることが少ないように思います。可読性が良い悪いということがうまくイメージできなければ「コードの上で迷子になりにくいプログラミング言語なのか、迷子になりやすいプログラミング言語なのか」と考えていただければ良いかと思います。

 3章で「オブジェクトは仕様が大きくなればCPUへの負荷なども増えるのでコンパクトなほうが有利です」と書きました。デリゲートやターゲットアクションでプログラムを構築していけば確かにイベント(ユーザからの操作)を受け付けつけるオブジェクトの仕様が大きくなることは抑えられます。しかし代わりにコントローラオブジェクトなどの仕様は増大することになります。仕様規模の大きさうんぬんだけではObjective-Cがなぜ継承をあまり使わないように設計されているのかの説明にはなりません。やはり可読性を重視したのかなと思います。前述の例ではサブクラス化することを前提として設計されたOOPとサブクラス化をできるだけ行わないようにしているObjective-Cのそれぞれのメリットとデメリットを述べましたが、個人的意見としては「慣れれば圧倒的にObjective-Cのほうが読みやすい」と思います。しかしactionメソッドをはじめとするトリガーメソッドを外部に出してしまっているということを理解するというか、そのことに慣れるまでにはある程度時間がかかると思います。

ウィンドウに編集済みマークを表示する

ターゲットアクションはInterface Builderを使わずにコーディングによって接続することもできます。しかしその方法を説明する前に気になることがあります。それはText Viewをを編集すればメインウィンドウのクローズボタン(クローズボックスとも呼ばれます)に編集された事を表す黒いドットが現れ、Indicateボタンを押すと編集済みの黒いドットが消えるという機能をMyClipに搭載したいということです。

 またこの項では「リファレンスを見ながらコーディングする」ということにも慣れていきたいと思います。

リファレンスの表示

 リファレンスはXcodeの「ヘルプ」メニューから「デペロッパドキュメント」を選ぶか、もしくは command + option + ? で表示することができます。「?」はおそらくシフトキーを押さなければならないと思いますので、最終的には command + option + shift + ? を押す事になります。"Xcode Quick Start"ウィンドウが現れましたら右上の"Search"フィールドにクラス名やメソッド名を入力すると目指すリファレンスが現れます。もし英語が苦手でリファレンスに書かれていることが記号にしか見えなくても、プログラミング言語そのものが記号みたいなものですから、気にせず眺めていれば得るところはそれなりにあると思います。少なくとも引数や戻り値の型が分かるだけでも助かります。


【Xcode 3.1の場合】

 リファレンスはXcodeの「ヘルプ」メニューから「製品ドキュメント」を選ぶか、もしくは command + option + ? で表示することができます。左ペインのドキュメントセットで1番上の「Apple Mac OS X 10.x」を選んで検索フィールドにクラス名やメソッド名を入力すると目指すリファレンスが現れます。

 ではまずNSWindow Class Referenceを開いてください。そして下へスクロールしていくとAccessing Edited Statusグループに-setDocumentEdited:というメソッドを見つけることができます。いきなり答えを出しますがこのメソッドがウィンドウを編集済みに設定したり、あるいはその設定を解除するメソッドです。

図 NSWindow Class Reference - setDocumentEdited1

 メソッド名はリンクになっています。クリックするとそのメソッドの詳細が現れます。

図 NSWindow Class Reference - setDocumentEdited2

 このメソッドの完全なシグネチャは次のようになっていることが分かります。

	- (void)setDocumentEdited:(BOOL)documentEdited

 このシグネチャからも推測できるようにウィンドウにはBOOL型のdocumentEditedというインスタンス変数があるみたいです。それをYESに設定するとウィンドウは編集済みとなりクローズボタンに黒いドットが表示され、NOに設定するとウィンドウは未編集になり黒いドットは消えます。

 インスタン変数の値を取得するメソッドの名前はそのインスタン変数名とまったく同じにする命名規則になっています。ただしBOOL値については「isインスタンス変数名」となっていることが多いみたいです。なおBOOL値の場合でもインスタンス変数名だけで値を取得できます。このことについては実践編の「キーバリューコーディング」で説明されています。

 メソッドのシグネチャは分かりました。次にこのメソッドをメッセージとして送るレシーバを用意する必要があります。それにはいくつかの方法がありますが、まず最初に思いつく方法はCotroller.h のブロック文の { と } の間、つまりインスタン変数を宣言する場所に次のように記述する方法です。強調箇所が追加するコードになります。なおsetDocumentEditedメッセージのレシーバは当然ウィンドウになります。

#import <Cocoa/Cocoa.h> #import "Model.h" @interface Controller : NSObject { Model *model; IBOutlet NSTextView *textView; IBOutlet NSWindow *window; } - (IBAction)indicate:(id)sender; @end

 このコードについてはもう説明する必要はないと思います。IBOutletと記述することによってInterface Builderでそのインスタン変数を認識できるようになりMainMenu.xibウィンドウ内のほかのオブジェクトに接続できるようになります。そしてIBOutletという文字列はコンパイルに先立つプリプロセスで削除されます。

 しかし今回は別のレシーバを用意します。というかすでに存在しているメインウィンドウを指し示すインスタン変数を使用します。
※もし前述のコードをすでに追加されていた場合は削除しておいてください。

 ビューやコントローラなどのGUI部品はウィンドウの上(中)に配置されていなければ表示できないことになっています。そしてこれらのGUI部品は自分が配置されているウィンドウを指し示すwindowというインスタンス変数を持っています。インスタンス変数のゲッターメソッドは変数名と同じでしたね。window変数を取得するwindowメソッドをリファレンスで調べてみましょう。製品ドキュメントを表示して検索フィールドにNSTextViewと入力するとNSTextView Class Referenceが表示されます。しかしいくら探してもウィンドウを取得するwindowというメソッドは見つかりません。このような場合にはスーパークラスでも探してみましょう。リファレンスの冒頭箇所のInherits fromグループからひとつ上のクラスNSTextをクリックしてNSText Class Referenceに移動します。しかしNSTextにもwindowメソッドは見つかりません。さらにもうひとつ上のクラスに移動してみましょう。Inherits fromで次のNSViewをクリックしてください。NSView Class Referenceが表示されまます。windowメソッドを探すと今度はすぐに見つかります。詳細も見てみましょう。シグネチャは次のとおりです。

	- (NSWindow *)window

 これでメッセージの送り先も見つかりました。さっそくコーディングしてみましょう。

Controller.mのtextDidChangeメソッドに、次のように強調表示されているコードを追加ししてください。

- (void)textDidChange:(NSNotification *)aNotification { [model setString:[[textView string] copy]]; [[textView window] setDocumentEdited:YES]; }

 setDocumentEditedのBOOL値をYESにすればクローズボタンに編集済みの黒いドットが現れます。

 次にindicateメソッドにDocumentEditedのBOOL値をNOに設定するコードを書き加えたいと思います。Buttonオブジェクトもウィンドウの上(中)にあります。TextViewのときと同じようNSButton Class Referenceを表示し、継承関係をさかのぼりながらwindowゲッターメソッドを探してください。

 今回もNSButton→NSControl→NSViewと2階層上のTextViewの時と同じNSViewの中にwindowメソッドが見つかりました。これは同じクラスを継承しているのだから当然とも言えます。

 では、次のようにindicateメソッドに強調表示されたコードを追加してください。

- (IBAction)indicate:(id)sender { NSLog(@"%@", [model string]); [[sender window] setDocumentEdited:NO]; }

 indicateメソッドは、メソッドの送り手自身を表すsenderを引数としています。IBActionを戻り値にすることによってInterface Builderで接続が行えるようにされたメソッドは必ずメッセージの送り手自身をsenderという名前で引数としなければならない決まりになっています。強調表示されたコードでは送り手sender、つまりPush Buttonに対してwindowメソッドを送信してメインウィンドウへの参照(ポインタ)を取得しています。そしてその参照(メインウィンドウ)のdocumentEditedの値をNOに設定しています。これによりメインウィンドウの編集済みマークは消えることになります。なおこのindicateメソッドでもtextViewにwindowメソッドを送って

	[[textView window] setDocumentEdited:NO];

 としてもまったく問題はありません。Text Viewが乗っているウィンドウもPush Buttonが乗っているウィンドウも同じものです。ここではウィンドウへの色々なアクセス方法があることを示すためにコードを変えているだけです。

 では、コンソールウィンドウを表示して「ビルドして進行」をクリックしてください。メインウィンドウに文字を入力して確定するとウィンドウのクローズボタンに黒いドットが現れて編集済みであることが示されます。Indicateボタンを押すとコンソールにModelオブジェクトの内容が書き出されてウィンドウの編集済みマークの黒いドットは消えます。

 お疲れさまでした :-)。うまく動作いたしましたでしょうか。



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


Copyright 2006 - 2010 viva Cocoa. All Rights Reserved.