viva Cocoa Objective-C 入門 第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ウィンドウが開きます。もし開いていなければデスクトップに出来上がっている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つあります。

  1.  読み込みプリプロセッサがC言語のincludeからObjective-Cのimportに変わっています。importでヘッダファイルを読み込んだ場合はすでに読み込んでいるヘッダファイルを再び読み込まないことを保証することになっています。includeの場合はincludeするたびにヘッダファイルが読み込まれます。結果として同じ関数などが重複して定義されることになりエラーを起こす場合があります。importはこの問題を回避しています。

  2.  Cocoa.hというヘッダファイルはCocoaフレームワークの中で最も重要な2つのフレームワーク(ヘッダファイルの集合体)であるFoundation.hとAppKit.hを合わせたものです。今回のModelクラスで必要となるヘッダファイルはFoundationフレームワークに含まれているNSObject.hとNSString.hだけです。しかしアプリケーションを作成する場合には、あとからコーディングが追加・変更される場合も想定して最初からCocoa.hを読む込むことにしておいたほうが良いでしょう。不必要なヘッダファイルを読み込むことは、人間には感知することができない程度の余計な時間がコンパイル時にかかるかもしれませんが、実行ファイルのパフォーマンスに影響を与えることはありません。

■ インタフェース部

 最初の@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

 この記述でインタフェース部の終わりを表しています。


■ シグネチャ(signature)

 シグネチャの一般的な意味は「署名」になりますが、プログラミング用語としては戻り値の型から始まる関数(メソッド)名、引数までの、そのメソッドの宣言や定義で使われる一連の正式な文字列のことを表します。

 実際にシグネチャの各部分を図示してみます。次のメソッドは矩形(四角形)の縦横の長さを設定するメソッドだと仮定してください。

図 シグネチャ

 広義の意味では最初に説明したように図シグネチャの文字列全体がシグネチャになります。しかし狭義の意味では図の「メソッドシグネチャキーワード」と呼ばれる部分がシグネチャと呼ばれる場合もあります。

      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を見てください。

  1. Xcodeを起動して「ヘルプ」メニューから「デベロッパドキュメント」を選択すると“Xcode Quick Start”ウィンドウが開きます。“Search”フィールドに“NSTextView”と入力してください。

    図 Xcode Quick Start


  2. NSTextView Class Referenceが表示されます。しかしずっと下にスクロールしていってもプレーンなテキストを設定・取得するメソッドは見当たりません。Inherits fromという項目にはルートクラスまでのすべてのスーパークラスのリンクが並んでいます。ひとつ上のスーパークラスのNSTextをクリックしてNSText Class Referenceに移動してください。

    図 NSTextView Class Reference


     NSText Class Referenceに切り替わります。下へスクロールしていくと今度は“Tasks”項目の“getting the Characters”グループに“string”メソッドが見つかります。さらに下へスクロールすると“Replacing Text”グループに“setString:メソッドも見つかります。このsetString:とstringはプレーンテキストの設定と取得をするメソッドです。

 このようにしっかりとリファレンスを見て行かないとメソッドを見落とすことがありますので注意してください。

 また今回の場合は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; }

 この実装コードの意味は分かりづらいものだと思います。詳しくは次章以降で説明することになりますが、おおよその説明をすると次のようになります。

  1. 最初に継承階層をルートクラスつまり NSObject までさかのぼります。

  2. そして self の中に自己を表す値を代入します。この場合の自己とは NSObject のことではありません。最初にこの init メソッドをメッセージとして受け取ったレシーバのオブジェクトのことです。この代入によって self がそのオブジェクト自身を表すようになります。

  3. 次に継承階層を降りていき、それぞれのクラスで上書きされた init メソッドにしたがってインスタン変数の値を初期化していきます。

  4. 最後に、最初に init メッセージを受け取ったオブジェクトを上書きされた init メソッドにしたがってインスタン変数を初期化して自分自身すなわち self を返します。

 繰り返しの説明になりますがインスタン変数がメモリに確保されるのは alloc メソッドが実行された時点です。( alloc メソッドが実行された時点でインスタンス変数がメモリに確保されます)。したがってinitメソッドを実行せずにオブジェクトを使い始めることはできます。しかし継承階層の中でどのような大事な初期化がされているか分かりません。初期化メソッドの実行は必ずしておくべきことです。

 なお、確保だけされた変数(初期化されていない変数)がどのような値を持つかは処理系に依存することになります。多くの場合は 0 もしくは 0 を表す値が変数を確保した時点で代入されるケースが多いですが確実なことではありません。実際に Xcode でも 0 が代入される変数もあればそうでない変数もあり、まちまちな状態だと言えます。したがって 0 で初期化されることをアテにしてのコーディングは当然のことながらやめておくべきでしょう。


処理系

 狭義の意味ではコンパイラやインタープリタだけを表す言葉。広義の意味では IDE(統合開発環境)全体を表す場合もありますが、多くの場合は狭義の意味で使われます。なお実装系と呼ばれることもあります。

Column Objective-C 2.0 の新機能

 2007年9月にMac OS X 10.5 Leopardと一緒に発表されたObjective-C 2.0の新機能は次のとおりです。

1. ガベージコレクション
 日本語に訳せば「ゴミ集め」となります。従来Objective-Cではリファレンスカウント方式という手法で不要になった変数が占有しているメモリ領域の解放をおこなっていました。しかしこの手法の使用については賛否両論がありました。そこでObjective-C 2.0ではガベージコレクションという方式を採用して、不要になった変数が占有しているメモリ領域の解放をプログラマが一切コーディングすることなく自動でおこなう手法も取り入れました。
 現在はリファレンスカウント方式とガベージコレクション方式の両方から選択して使う形になっています。しかし残念ながらiPhoneのObjective-Cではガベージコレクションは使えません。ガベージコレクションはそこそこのCPU負荷がかかるからです。なおJavaではJava登場の時からガベージコレクションだけを採用しています。

2. プロパティ
 アクセサメソッドの自動合成機能です。アクセサメソッドはかなり定型化された定義になっています。インスタン変数が多数の場合にはそのようなメソッドをわざわざコーディングするのは面倒なことです。そこでこのアクセサメソッドの自動合成機能が搭載されました。
 なお同時に条件付きではありますが、ほかのオブジェクト指向プログラミングでは標準となっているドット演算子によるコーディングもできるようになっています。ただし内実はメッセージ送信を表面上だけドット演算子による記述に変えているだけになっています。

3. 高速列挙
 コレクションと呼ばれるデータの集合体を順次参照するための新しいループ制御文です。for文よりも記述がシンプルでかつ実行速度が速いということになっています。

 本書では第12章までこれらの新機能は使わずにプログラミングしていきます。そして第13章から順次これらの新機能を取り入れていきます。
 これら3つの新機能は記述がやさしく、また概念的に覚えるのも楽なので難しいことから先にやっておこうという考えと、iPhoneプログラミングではガベージコレクションが使えないことを考えてのこのような構成にいたしました。



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


Copyright 2006 - 2010 viva Cocoa. All Rights Reserved.