第3章 Objective-C | ||
ホーム | メール | |
目次 | < 前ページ 次ページ > |
前節まではObjective-Cのオブジェクトの概要について説明をしてきました。この節からオブジェクトの素(設計図)であるクラスについて実際のコードを示しながら説明していきます。
クラスの構造を図示しようとしても前節で示したあの簡素な(簡素すぎる?)「各言語のオブジェクトの構造」と同じになるでしょう。そこでここではクラスを定義する実際のコードを示しながらクラスの説明をしていきたいと思います。
Cocoaでアプリケーションを作る場合、どんなに少なくともクラスをひとつ自分で作成することになるでしょう。次章からは「MyClip」という簡素なメモ(覚え書き?)アプリケーションを作成しながら説明をしていきます。そしてそのアプリケーションでは Model というクラスと Controller というクラスを自作する予定です。この節ではそのうち Model クラスのコーディングをおこないながらクラスの説明をしていきたいと思います。
【Xcode 3.1の場合】
Delegateというクラスも作成してもらうことになります。
Xcodeを起動してください。Dockに登録したアイコンをワンクリックすると起動します。もし Welcome to Xcodeウィンドウが表示されたら閉じていただいて結構です。「ファイル」メニューから「新規ファイル...」を選んで「新規ファイル」ウィンドウを表示させます。
左ペインでMac OS Xの“Other”を選び、右ペインで“空のファイル”を選びます。そして「次へ」をクリックしてください。
【Xcode 3.1の場合】
Xcode 3.2と同じく左ペインで“Other”を選び、右ペインで「空のファイル」を選びます。
図 新規ファイルウィンドウ1
次に現れる「新規 空のファイル」保存ウィンドウでファイル名を“Model.m”にします。保存場所では「選択...」ボタンをクリックしてデスクトップを選択します。そして「完了」ボタンをクリックしてください。
【Xcode 3.1の場合】
Xcode 3.2の場合とまったく同じです。
図 新規ファイルウィンドウ2
何も書かれていないModel.mウィンドウが開きます。もし開いていなければデスクトップに出来上がっているModel.mをダブルクリックしてください。そして次のように記述します。
#import <Cocoa/Cocoa.h> @interface Model : NSObject { NSString *string; } - (void)setString:(NSString *)aString; - (NSString *)string; @end @implementation Model - (void)setString:(NSString *)aString { [string release]; string = aString; } - (NSString *)string { return string; } @end
このホームページではソースコードは、おそらく "Courier" というフォントで表示されていると思いますが、新しいXcode 3.2.xのデフォルトでは“Menlo Regular”というフォントで表示されます。そして Xcode 3.1 では“Monaco” というフォントで表示されていると思います。
コードファイルの冒頭でコンパイラにとって必要となるヘッダファイルを読み込んでいます。
#import <Cocoa/Cocoa.h>
ここでは注目すべき点が2つあります。
最初の@interfaceから@endの部分をインタフェース部と呼びます。このインタフェース部が、各フレームワークに含まれている各クラスのヘッダファイルになります。
@interface Model : NSObject
@interfaceに続いてクラス名を記述します。クラス名については次のような命名規則があります。
そしてクラス名のあとにコロン“:”とスーパークラス名を記述します。
Objective-Cのクラスは、スーパークラスを1つだけ持ちます。このことを単一継承と呼びます。他の主なOOPを例に出すとJavaも単一継承です。しかしC++は複数のスーパークラスを持つことができます。このことを多重継承と呼びます。なおスーパークラスについては「オブジェクト指向プログラミング言語の3つの特徴」でも説明しましたが、次項「クラスの継承」で再度説明いたします。
話しを戻します。今回の例ではNSObjectというクラスをスーパークラスに指定しています。Objective-Cでは特に継承したいクラスがない場合は必ずこのNSObjectを継承することになっています。各クラスが希望のクラスかもしくはNSObjectを継承することにしておくと最後にはすべてのクラスが(すべてのクラスのスーパークラスをたどっていけば)このNSObjectにたどりつくことになります。そしてNSObjectにはスーパークラスは存在しません。このようにスーパークラスを持たないクラスのことをルートクラスもしくは基底クラスと呼びます。そしてこの NSObject にはすべてのオブジェクトに必要となるallocやinit 、そして先ほどオブジェクトの比較をするために使ったisEqual:メソッドなどオブジェクトとして振舞うために必要な様々なメソッドが定義されています。
{ NSString *string; }
このように波括弧 { } の間にインスタン変数を宣言していきます。このModelクラスではインスタン変数はひとつですが、宣言できる変数の数に制限はありません。インスタン変数の宣言方法はC言語の変数の宣言方法と同じですが、ひとつ注意しなければならないことがあります。オブジェクトを代入する変数はそのオブジェクトのクラスのポインタを型にするかid型にしなければいけないということです。なお「オブジェクトの構造」のところでも言いましたがインスタン変数を「メンバ変数」と呼ぶ場合もあります。またこのインスタン変数はそのクラス(オブジェクト)内でグローバルな変数になっていることを理解しておいてください。
- (void)setString:(NSString *)aString; - (NSString *)string;
インスタン変数の宣言が終われば、次にそのクラスで新たに定義されるメソッドのメソッド宣言と呼ばれるものを記述していきます。このメソッド宣言とはC言語のプロトタイプと同じ働きをします。ADCで配布されているガイドの中にはこのメソッド宣言をプロトタイプと記述されているものもあります。メソッド宣言の記述方法はC言語のプロトタイプの記述方法と同じです。メソッドの完全なシグネチャの最後にセミコロン“;”を付けます。シグネチャについてはこの後で説明します。なおこの Model クラスでは string インスタン変数のセッターメソッドとゲッターメソッドの2つだけを宣言しています。
@end
この記述でインタフェース部の終わりを表しています。
シグネチャの一般的な意味は「署名」になりますが、プログラミング用語としては戻り値の型から始まる関数(メソッド)名、引数までの、そのメソッドの宣言や定義で使われる一連の正式な文字列のことを表します。
実際にシグネチャの各部分を図示してみます。次のメソッドは矩形(四角形)の縦横の長さを設定するメソッドだと仮定してください。
図 シグネチャ
広義の意味では最初に説明したように図シグネチャの文字列全体がシグネチャになります。しかし狭義の意味では図の「メソッドシグネチャキーワード」と呼ばれる部分がシグネチャと呼ばれる場合もあります。
setRectangleWidth: height:
またこの部分は「メソッドシグネチャ」と呼ばれる場合もあります。通常は、あるメソッドを表す場合にはこの狭義の意味でのシグネチャを使います。そしてコーディングを行うために正式なシグネチャを知りたい場合に「シグネチャ」と言った場合には広義の意味での次のシグネチャことを指しています。
- (void)setRectangleWidth:(int)widthSize height:(int)heightSize
本書ではシグネチャと言った場合には広義の意味でのシグネチャのことを表しています。そしてメソッド名を表す場合には狭義の意味でのシグネチャを使っています。なおここで注意していただきたいことがあります。それは、メソッド名を表すためにこの狭義の意味でのメソッドシグネチャを使うときには、そのメソッドに引数がある場合は必ずコロン”:”までつけるということです。コロンをつけることによってそのメソッドに引数がいくつあるのか分かるようになります(分かります)。
@implementationのimplementationはコンピュータ用語としては実装と訳されることが多いみたいです。そしてこの@implementationから@endの間を日本語訳ではインプリメンテーション部もしくは実装部と呼ぶこともあれば定義部と呼ぶこともあります。これらはたんに呼び方の違いだけです。
最近では呼び方が徐々に統一されてきて@interface部をインタフェース部もしくはヘッダ部。@implementation部を定義部と呼ぶようになってきています。そしてのちほど説明しますが実際にはこのインタフェース部と定義部は2つのファイルに分かれます。そしてそれぞれを宣言ファイル(もしくはヘッダファイル)と定義ファイル(もしくは実装ファイル)という呼び方にほぼ決まりつつあります。少しややこしいかもしれませんがすべて呼び方の違いだけで内容が変わるわけではありません。
ではこの@implementation部でやっていることは何でしょうか。それはメソッドの定義と実装です。部分(コード)ごとに説明していきます。
@implementation Model
@implementationの後に(メソッドの定義をする)クラスの名前を記述します。これは1つのファイルに記述すると考えれば違和感を覚えるかもしれませんが、実際には2つのファイルに分かれることを考えれば納得いただけると思います。
- (void)setString:(NSString *)aString { [string release]; string = aString; }
セッターメソッドの定義をおこなっています。メソッドのシグネチャはインタフェース部で行ったメソッド宣言のシグネチャと同じです。ただしメソッド宣言ではシグネチャの最後にセミコロン“;”がつきます。このことも含めてメソッド宣言はC言語のプロトタイプとまったく同じことを行っていることになります。メソッド宣言をする理由もプロトタイプを記述する理由と同じです。
{ [string release]; string = aString; }
セッターメソッドの実装内容です。2行目のstring = aStringは分かっていただけると思います。インスタン変数stringに引数で受け取ったaStringを代入しています。ただし代入されるのはNSString型のポインタです。Objective-Cでのオブジェクトのやり取りは常にポインタで行われることを忘れないでください。
問題は1行目のメッセージ式です。stringインスタン変数つまりオブジェクトにreleaseというメッセージを送っています。これはレシーバのオブジェクトの参照カウント(reference count)という値(整数値)をひとつ減らすというメッセージになります。詳しくはのちの章で説明いたしますがObjective-Cにはリファレンスカウント方式とガベージコレクション方式という2つのメモリ管理方式があります。releaseメソッドはこのうちリファレンスカウント方式でメモリ管理行う場合に使うメソッドのひとつです。このメッセージを受け取ったオブジェクトは自身の参照カウントの値をひとつ減らします。そしてこの値が0になるとそのオブジェクトは破棄され占有していたメモリ領域が解放されます。
このサンプルのModelオブジェクトの場合はstringインスタン変数の参照カウントの値が2以上になることはありません。したがってreleaseメッセージによってstringインスタン変数の参照カウントの値は0になりstringインスタン変数に代入されているオブジェクトは破棄されそのオブジェクトが占有していたメモリ領域は解放されます。そして次の代入式でstringインスタン変数に新たな参照先が代入されます。
string = aString;
このメモリ管理に関する話しは難しいと思います。章をあらためてのちほど説明いたしますが(6章で詳しく説明しますが)、Objective-Cを学習するうえでの壁のひとつと言えるでしょう。
参照カウント
ADCのリファレンスなどによると“reference count”となっています。そのまま直訳しましたが正確には「そのオブジェクトがほかのオブジェクトから参照されている数」ということになります。オブジェクトの参照カウントの値を取得したい場合はそのオブジェクトに retainCount メッセージを送ると戻り値として整数値が返ってきます。
[anObject retainCount]
なお長らく、このリファレンスカウント方式のメモリ管理システムにはこれと言った名称がついていませんでした。ADCのドキュメントでは“managed memory”と表現されていましたが、名称としてはピンとくるものではありませんでした。そこで書籍によって「保持カウント」「リファレンスカウンタ」「リテインカウント」とさまざまな便宜的な名称でこのメモリ管理システムを呼んでいました。しかしXcode 3.2(Mac OS X 10.6 )になって“reference-counted memory management system”という表現が見られるようになってきました。今後この英文がどのように翻訳されていくようになるのかは分かりませんが、本書では「リファレンスカント方式」、もしくは「参照カウント」と呼ぶことにいたします。
- (NSString *)string { return string; }
ゲッターメソッドの定義をおこなっています。実装の内容についての説明は必要ないと思いますが return で返しているのは NSString型のポインタであることは忘れないでください。
最後に@endで定義部を終了します。
すべてのオブジェクト指向プログラミング言語は継承という仕組みを持っています。Objective-Cでは必ず任意のクラスを1つ継承して新しいクラスを作る決まりになっています。新しく作ったクラスは継承元のクラスのすべてのインスタンス変数とすべてのメソッドを継承します。そして新しいインスタンス変数と新しいメソッドを追加することができます。また継承したメソッドの定義を変更することもできます。この継承したメソッドの定義を変更することを上書き(override)と呼びます。
継承元となったクラスをスーパークラスもしくは親クラスと呼びます。そして継承によって作られた新しいクラスをサブクラスもしくは子クラスと呼びます。
新しいクラスを作る時にとくに継承したいクラスがない場合はNSObjectを継承することに決まっています。このように希望のクラスかNSObjectを継承することに決めておくとすべてのクラスのスーパークラスをたどっていけば必ずNSObjectにたどり着くことになります。そしてNSObjectにはスーパークラスは存在しません。このNSObjectのようにスーパークラスが存在しないクラスのことをルートクラスもしくは基底クラスと呼びます。Objective-CではNSObjectが唯一のルートクラスになります。
このようにしてNSObjectはすべてのクラスに継承されることになりますが、NSObjectにはインスタンスの確保、初期化を行う“alloc” “init”メソッド。およびリファレンスカウント方式のメモリ管理をおこなう“retain” “release” “autorelease” “dealloc”など、オブジェクトとして振る舞うための基礎となるメソッドが定義されています。オブジェクトの比較をするisEqual:メソッドもNSObjectで定義されています。
また、Objective-Cのように 1つのクラスだけを継承すること(親とすること)を単一継承と呼びます。Javaも単一継承になります。それに対して複数のクラスを継承できるシステムを多重継承と呼びC++はこれにあたります。なお単一継承でもサブクラス(子クラス)はいくつでも作ることができます。
この副項をはじめる前に、第1章 第5節「Xcodeの環境設定」の「ドキュメント」の項を参考にして、リファレンスの更新をおこなってください。
継承した親クラスのインスタン変数とメソッドをあらためて子クラスで記述する必要はありません。記述されていなくても継承したインスタン変数とメソッドはサブクラスに間違いなく存在しています。しかしここで注意しなければいけないことがあります。それは継承したメソッドについてはリファレンスにも掲載されないということです。
次章からは「MyClip」という簡素なメモアプリケーションをサンプルとして作成しながらObjective-Cの説明を続けていくことになります。そこではメモを書くための画面としてNSTextViewというAppKitクラスのインスタンスを使います。
ところがNSTextViewには純粋なテキストだけを設定・取得するメソッドは定義されていません。次の方法でNSTextView Class Referenceを見てください。
このようにしっかりとリファレンスを見て行かないとメソッドを見落とすことがありますので注意してください。
また今回の場合はNSTextViewもNSTextもたまたまMac OS Xだけに存在するクラスでした。しかしiPhone OSにも存在するクラスも多く、リファレンスが見にくい場合があります。そういう場合は“All Doc Sets”と“All Languages”プルダウンメニューでリファレンスに表示させるOSと言語を絞り込んでおくことができます。
図 NSText Class Reference
All Doc Setsプルダウンメニューから“iPhone OS 3.1 Library”のチェックを外してください。
図 All Doc Sets
All Languagesプルダウンメニューからは“Objective-C”以外のすべてのチェックを外してください。
図 All Languages
これでそれぞれのタイトルは“2 of 3 Doc Sets”と“Objective-C”に変わります。今後本書ではこの状態で説明を進めていきますので、このように変更しておいてください。
【Xcode 3.1の場合】
Xcode 3.1.xを起動して「ヘルプ」メニューから「製品ドキュメント」選んでください。次に現れる「デベロッパドキュメント」ウィンドウで「検索」フィールドに“NSTextView”と入力すると“NSTextView Class Reference”が現れます。スーパークラスへの移動などはXcode 3.2の場合と同じですが、OSや言語の絞り込みの設定方法は異なります。しかし本書ではこの設定方法の説明は省略させてもらいます。Xcode 3.2と比べるとXcode 3.1のリファレンスの設定方法は分かりやすいと思います。
図 NSTextView Class Reference Xcode 3.1
クラスの説明の最後として継承の次に大事であると思われるメソッドの上書きについて、概要を説明いたします。詳細については次章からのサンプルアプリケーションのなかで実例として説明していきたいと思います。
次の式は「ランタイムシステムとメッセージ式」の中で登場してきたクラスからインスタンスを生成して初期化するネスト(入れ子)されたメッセージ式です。
variable = [[AClass alloc] init];
前項の「クラスの継承」でも説明しましたが、このメッセージ式の中で使われている alloc メソッドも init メソッドも NSObject クラスの中で定義されています。NSObject Class Reference で確認するとこの2つのメソッドのシグネチャは次のようになっています。
+ (id)alloc - (id)init
この2つのシグネチャも「メッセージ式」のところで掲載したものと同じものですが、NSObject で定義されているということはすべてのクラスがこの2つのメソッドを持っていることになります。そしてこのうち init メソッドはほぼ各クラスで上書きされることになります。
initメソッドの定義は次のようになっています。
- (id)init { self = [super init]; if (self) { // 自己のインスタン変数の初期化 } return self; }
この実装コードの意味は分かりづらいものだと思います。詳しくは次章以降で説明することになりますが、おおよその説明をすると次のようになります。
繰り返しの説明になりますがインスタン変数がメモリに確保されるのは alloc メソッドが実行された時点です。( alloc メソッドが実行された時点でインスタンス変数がメモリに確保されます)。したがってinitメソッドを実行せずにオブジェクトを使い始めることはできます。しかし継承階層の中でどのような大事な初期化がされているか分かりません。初期化メソッドの実行は必ずしておくべきことです。
なお、確保だけされた変数(初期化されていない変数)がどのような値を持つかは処理系に依存することになります。多くの場合は 0 もしくは 0 を表す値が変数を確保した時点で代入されるケースが多いですが確実なことではありません。実際に Xcode でも 0 が代入される変数もあればそうでない変数もあり、まちまちな状態だと言えます。したがって 0 で初期化されることをアテにしてのコーディングは当然のことながらやめておくべきでしょう。
処理系
狭義の意味ではコンパイラやインタープリタだけを表す言葉。広義の意味では IDE(統合開発環境)全体を表す場合もありますが、多くの場合は狭義の意味で使われます。なお実装系と呼ばれることもあります。
2007年9月にMac OS X 10.5 Leopardと一緒に発表されたObjective-C 2.0の新機能は次のとおりです。
本書では第12章までこれらの新機能は使わずにプログラミングしていきます。そして第13章から順次これらの新機能を取り入れていきます。
これら3つの新機能は記述がやさしく、また概念的に覚えるのも楽なので難しいことから先にやっておこうという考えと、iPhoneプログラミングではガベージコレクションが使えないことを考えてのこのような構成にいたしました。
目次 | < 前ページ 次ページ > |