補足資料B
補足資料B もしくはSupplement B
プロトコル
1. プロトコル
●プロトコルの概略
プロトコルはある目的のために必要となるメソッドの宣言ををまとめたものです。プロトコルもターゲットアクションやデリゲートと同じくObjective-Cのデザインパターンのひとつになります。しかしターゲットアクションとデリゲートは既存のオブジェクトにメソッドを追加する方法と言えますがプロトコルは新しいクラスの設計をするときに(作成時に)ある目的のためにまとめられた既存のメソッドを取り入れる方法と言えます。そしてメソッドを実装する場所もターゲットアクションやデリゲートとはことなり、そのオブジェクト自身の中になります。またデリゲートは用意されているデリゲートメソッドの中から必要となるメソッドだけを実装すれば良いのに対して、プロトコルは取り入れたメソッドはすべて実装しなければならないという点も違います。
なおJavaのインタフェースと呼ばれる技術は、このObjective-Cのプロトコルがもとになったと言われています。
●プロトコルの採用
前段落で「プロトコルを取り入れる」という言葉と使いましたが、このことを正式にはプロトコルの採用と呼びます。ここではこのプロトコルの採用方法を先に説明することにいたします。実例としてMyClipにNSCodingプロトコルを採用しながら説明を行っていきたいと思います。第10章でも説明しましたようにMyClipにNSCodingプロトコルを採用する必要はありません。しかし採用しても問題が起こることもありません。
現在、読者がどの章まで学習が進んでおられるかは私のほうでは分かりませんが一応10章第2節まで終わっているものとして話しをすすめていきます。まず10章第2節のプロジェクトをフォルダごとコピーしてください。そしてコピーしたフォルダに「Spplement B」などの分かりやすい名前をつけてください。そしてSupplement Bフォルダの中のMyClip.xcodeprojを開きます。今回、プロトコルを採用するのはModelオブジェクトになります。Mdel.hを開いて次のようにコーディングします。いつものように強調箇所が追加・変更されるコードです。
Model.h
#import
<Cocoa/Cocoa.h>
@interface
Model : NSObject <NSCoding> {
NSTextStorage *string;
}
-
(void)setString:(NSTextStorage *)aString;
-
(NSTextStorage *)string;
@end
□コード説明
@interface Model : NSObject <NSCoding>
このようにスーパークラス名にあとに山括弧
< > で囲んでプロトコル名を記述すればそのプロトコルを採用したことになります。そしてこのことを「プロトコルの採用」と呼びます。複数のプロトコルを採用したい場合は
<プロコトル名, プロトコル名, プロトコル名>
と山括弧の間にカンマ(,)で区切って採用したいプロトコル名を並べて記述します。
●プロトコルの宣言
プロトコルはプログラマが自分で宣言することもできます。次のように記述したファイルをヘッダファイル(プロトコル名.h)として保存します。プロトコル名は英文字でなければなりません。
@protocol プロトコル名
メソッド宣言 ;
メソッド宣言 ;
...
@end
NSCodingプロトコルの場合は次のような宣言になっています
@protocol
NSCoding
-
(void)encodeWithCoder:(NSCoder *)aCoder;
-
(id)initWithCoder:(NSCoder *)aDecoder;
@end
プロトコル名は既存のプロトコルと同じにならないように慎重に決めなければなりません。このプロトコルを採用する場合には、採用したいクラスのヘッダファイルで
#import “プロトコル名.h”
のようにそのプロトコルのヘッダファイルをよみこみます。なおNSCodingをはじめとする既存のプロトコルは <Foundation / Foundation.h> ですでに読み込まれています。
そしてあとは既存のプロトコルと同じように
@interface クラス名 : スーパークラス名 <プロトコル名>
と記述して「プロトコルの採用」行います。なお、この一連の作業工程を見るとプロトコルがたんなるメソッド宣言の代わりであることがわかっていただけると思います。
●プロトコルの意義
説明の順序が逆になりましたがそもそもプロトコルにはどういう利点があり何のために使うのでしょうか。メソッドをコントロールするためのデザインパターンは大きく2つに分かれます。ひとつはターゲットアクションとデリゲートのようにメソッドの実装を外部のオブジェクトに委ねることを主目的にしているもの。もうひとつはメソッドの整理や、あるオブジェクトがどういう機能に対応しているかを明確にすることを主目的にしているものです。このうちプロトコルはプロトコルを採用したクラスがどういう機能に対応しているか(どういうメソッドを実装しているか)を明確にするという目的が大きいと言えます。
またカテゴリと呼ばれるデザインパターンでは実装されているメソッドを整理するという目的が大きいと言えるでしょう。この「カテゴリ」については実践編で詳しく説明されています。ぜひご覧になってください。
もう少し説明を続けます。たとえばプロトコルにはオブジェクトをバイナリデータへ変換するためのメソッド宣言をまとめた「NSCoding」やオブジェクトをコピーできるようにするためのメソッド宣言をまとめた「NSCopying」などがあります。これらのプロトコルはClass ReferenceのConforms toグループを見れば採用されているかどうかが分かります。またヘッダファイルを見ても分かります。そして採用されていればそれらの機能に対応していることになります。「プロトコルの採用」は必ずすべてのメソッドの実装も行わなければならないことになっっていたことを思い出してください。つまりその機能に対応できるようになっていることを保証していることになります。実際にNSStringクラスではNSCodingとNSCopyingに適合(コンピュータ用語では対応よりも適合という言葉が使われます)していることがNSString Class Referenceを見れば分かります。またNSTextViewクラスでもNSCodingに適合していることが分かります。
ではなぜすべてのクラスを最初からこれらのプロトコルに適合させておかないのでしょうか。その答えはいつもの繰返しになりますが、必要になる可能性の低い機能はできるだけ搭載せずにクラスの規模を小さくしておくためです。逆に言えばNSStringやNSTextViewにNSCodingプロトコルが採用されていることはこれらの機能を使う可能性がきわめて高いことを表しているとも言えます。
●ターゲットアクション・デリゲート・プロトコルの違いを再確認しておきましょう
ここでターゲットアクション・デリゲート・プロトコルという3つのデザインパターンの違いについて整理しておきましょう。これらのデザインパターンにとって実装を外部のオブジェクトに行うのか、あるいはそのオブジェクトの内部に行うのかということは大きな違いだとも言えますが、単純に考えれば既存のオブジェクトに継承を使わずに実装を追加するためには外部のオブジェクトを使わざる得ないとも言えます。またこの3つのデザインパターにはそのこと以外にも違うところは少なからずあります。ここではそれらの違いを表にまとめてみました。
表.各デザインパターンの違い
|
ターゲットアクション |
デリゲート |
プロトコル |
メソッド宣言 |
外部のオブジェクトに自分で宣言します |
オブジェクトに用意されている宣言を利用します |
外部で作成された宣言を取り入れて利用します |
メソッド定義 |
外部のオブジェクト に定義します |
外部のオブジェクト に定義します |
そのオブジェクトの内部に定義します |
実装 |
自由に実装できます |
自由に実装できます |
決められたルールにしたがって実装します |
ターゲットアクションはtargetインスタン変数に登録した外部オブジェクトにメソッドの宣言・定義・実装を行います。メソッドのコーディングに関してはすべてまったく自由に行えます。
デリゲートではそのオブジェクトにデリゲート用として用意されいるメソッド宣言を利用します。プログラマはその用意されている宣言の中から作成中のプログラムにマッチするものを選んでdelegateインスタン変数に登録されている外部オブジェクトにその採用したデリゲートメソッドの定義と実装をコーディングします。用意されているメソッド宣言を利用しているためにメソッド定義を変更するわけにはいきませんが、実装はまったく自由にコーディングすることができます。また必要なメソッドだけを実装すれば良く、すべてのデリゲートメソッドを実装する必要はありません。
プロトコルは任意のオブジェクトをある目的に適合させるために「ある目的のためにまとられたメソッドの宣言をそのオブジェクトに取り入れる」ことと言えます。あるいは「プロトコルとはオブジェクトをある目的に適合させるために用意されたメソッド宣言のまとまりのこと」と言えるかもしれません。ようするにプロトコルの採用はたんにメソッド宣言の代わりをしているだけだと理解していただいて良いと思います。ただしそのことによってそのオブジェクトがどういう機能に適合しているのかが明白に分かるようになるというメリットは大きいです。
プロトコルとデリゲートとの違いにおいて、「プロトコルの採用」で取り入れたメソッド群はすべて実装しなければならないという点も大きいでしょう。しかしこの「宣言されたメソッドは必ず定義しなければならない」という決まりのほうがプログラミング言語の仕様としてはむしろ普通だと言えます。実際に本書でも自身で作成したクラスにデリゲートメソッドを組み込む方法は難度が高いために取り扱っていません。
●プロトコル採用の手順
この項の最後になりましたが、プロトコルを採用するための手順は次にあげることにします。なおプロトコルには「プロトコルの採用」という固有の作業を表す言葉がありますが、この言葉の使い方に気を配りすぎると説明文のほうが歪んだかたちになる場合があります。そこでこれ以降ではこの言葉をその固有の作業を指すこと以外にも使うことにします。どちらの意味で使っているかは適宜、文脈からご判断ください。
1. プロトコルの採用を行ってオブジェクトに搭載したい機能に必要となるすべてのメソッドの宣言を作成中のクラスに取り入れます。
2. 取り入れたメソッドはすべて実装します。
3. 実装にはそのプロトコルによって一定のルールがあります。リファレンスで確認しながらコーディングしていくようにします。
プロトコルの採用は「ある目的」に適合していることを表します。あるいは「ある目的」に適合していることを保証していることでもあります。したがってある一定のルールに基づいて実装していかなければならいことはご理解いただけることだと思います。
2. NSCoding
実質上プロトコルの説明はすでに終わっています。しかし前項では話しばかりだったのでここからは実例で学習していきます。さきほども言いましたがここではMyClipにNSCodingプロトコルを採用していきたいと思います。
●コード化しなければならないものは?
NSCodingはオブジェクトをコード化(バイナリデータ化)するメソッド群を持つプロトコルと思われているかもしれませんが、実際にコード化する必要があるのはオブジェクトのインスタン変数のデータだけです。またViewオブジェクト群とControllerオブジェクトはすべてxibファイルの中に含まれています。xibファイルに含まれているオブジェクトについてはアプリケーションによってコード化と非コード化されることになっています。つまりプログラマが気にする必要はないということです。そこで今回コード化しなければならいのはModelオブジェクトのインスタン変数だけということになります。
●用語説明
さきほどからコード化あるいは非コード化と呼んでいますが、ここで正式な呼び方を説明します。
エンコード(encode)
データを違う形式のデータに変換することをエンコードと呼びます。ここではインスタン変数のデータをバイナリデータに変換することを指しています。
デコード(decode)
変換されたデータを元のデータ形式に復元することをデコードと呼びます。ここではバイナリデータを元のインスタン変数のデータ形式に復元することを指しています。
●NSCodingプロトコルメソッド
ここまででエンコードとデコードしなければならないのはModelオブジェクトのインスタン変数だと分かりました。次にNSCodingプロトコルに用意されているメソッドを確認したいと思います。Xcodeの製品ドキュメント(snow leoparの場合は○○○で)でNSCoding Protocol Referenceを開いてください。全部実装しなければいけないとは言ってもNSCodingプロトコルのメソッドは2つだけであることは前述の「プロトコルの宣言」でも分かっています。それぞれのメソッドの正式なシグネチャを調べてみましょう。
- (id)initWithCoder:(NSCoder
*)decoder
バイナリデータを復元するデコードメソッドになります。戻り値としてid型すなわちオブジェクトを返します。
- (void)encodeWithCoder:(NSCoder
*)encoder
データをバイナリデータに変換するエンコードメソッドです。
これでメソッドのシグネチャも分かりました。いつもならさっそくコーディングを行うところですが今回はその前にさらに調べなければならないことがあります。それは前述の2つのメソッドで引数に使われているNSCoderというオブジェクトのことについてです。
3. NSCoder
NSCodingプロトコルではエンコードとデコード用の2つのメソッドを実装しなければいけないことになっています。しかしNSCodingプロトコルに用意されているのはこのエンコードとデコードを行うメソッドのシグネチャだけです。
次にこのNSCodingプロトコルのそれぞれのメソッドの中で実際にインスタン変数をバイナリデータ化したりバイナリデータをインスタン変数に復元したりしているオブジェクトがNSCoderです。
ここで一度NSCodingプロトコルメソッドを実装したModel.mのすべてのコードを掲載します。なおModel.hのほうはすでに「プロトコルの採用」ところで行ったコーディングですべてが終わっています。繰返しになりますがプロトコルの採用はメソッドの宣言を取り入れる行為です。したがってModel.hにおいてあらためてプロトコルメソッドのメソッド宣言を記述する必要性はまったくありません。
●Mdel.mコードリスト(第10章第2節のコードを基にしています)
いつものとおり強調箇所が追加・参考されたコードです。
#import "Model.h"
@implementation Model
- (void)setString:(NSTextStorage *)aString
{
[string
release];
string
= [aString copy];
}
- (NSTextStorage *)string
{
return
string;
}
/* NSCoding エンコードメソッド */
- (void)encodeWithCoder:(NSCoder
*)encoder
{
[encoder
encodeObject:string forKey:@"MyClipString"];
}
/* NSCoding デコードメソッド */
- (id)initWithCoder:(NSCoder
*)decoder
{
self
= [super init];
if
(self) {
string = [decoder decodeObjectForKey:@"MyClipString"];
}
return
self;
}
@end
コーディングが終わりましたらModel.mを保存してください。そして「ビルドして進行」ボタンをクリックしてください。MyClipは通常どおり起動して最後に保存したデータをテキストビューに表示すると思います。
いつものとおりに個別のコードの説明をする前に確認しておかなければならないことがあります。
NSCodingプロトコルを採用して実装すべきメソッドをすべて実装したオブジェクトは自分自身のインスタン変数をバイナリデータ化する機能と、バイナリデータからもとのインスタン変数のデータ形式に復元する機能を搭載できました。しかしその機能をいつ使うかはアーカイバとアンアーカイバと呼ばれるオブジェクトによってコントロールされます。ここまでの流れを整理すると次のようになります。
NSCodingプロトコルの採用
↓
NSCodingプロトコルのエンコードメソッドとデコードメソッドの実装
↓
アーカイバとアンアーカイバによるエンコードメソッドとデコードメソッドの呼び出し
この流れの中でアーカイバとアンアーカイバによるエンコードとデコードについては10章で説明されています。ここでは純粋にプロトコルに関係する部分だけの説明で終わります。この流れの中で言えば第2段目の説明でこの補足資料Bでの説明は終わることになります。
もうひとつ確認しておかなければならないことはこの補足資料Bのサンプルアプリケーションは正常に稼働しています。それは10章においてすでにアーカイバとアンアーカイバのコーディングが終わっているからです。
以上2点についての確認が終わったところでコード説明に移ります。
●コード説明
□エンコードメソッド
-
(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:string
forKey:@"MyClipString"];
}
このメソッドで使われているメッセージ式ではレシーバに引数として受け取った(NSCoder
*)型のencoderを使っています。NScoderはModel.hで読み込んだCocoa.hの中のFoundation.hの中のNSCoder.hで宣言されています。
NSCoding Protocol Referenceでメソッドのシグネチャが出ているところを表示してください。引数のNSCoderの文字列がリンクになっています。リンクをクリックしてください。
図:NSCoding Protocol Reference
NSCoder Class Referenceが現れると思います。下へスクロールしていくとEncoding Dataというエンコードメソッドを集めたグループが表示されます。
多くのEncodingメソッドが並んでいますが、その中から
encodeデータ型名: forKey:
となっているメソッドを選んでください。
例えば
encodeObject: forKey:
encodeDouble: forKey:
encodeFloat: forKey:
encodeInt: forKey:
などです。詳細はリファレンスでメソッド名をクリックして見てください。
図NSCoder Class Reference 1
これらのメソッドを選ぶ理由は次のとおりになります。
・エンコードメソッドとしてはMac OS X 10.2から採用になった比較的新しいものである
・古いエンコードメソッドではインスタンス変数の値をデータ化する場合も、そしてそのデータをデコーダメソッドで復元する場合も、そのオブジェクトのインスタンス変数の並び順にしたがって行わなければなりません。しかし新しいエンコードメソッドでは各インスタンス変数にforKey:を使ってそれぞれを識別するキー(文字列)を付けます。読み込みのときにはそのキーを使って復元した値を元のインスタンス変数に代入します。データ化順序や復元順序を気にする必要はありません。復元しないデータがあっても、そのほかのデータは正しく元のインスタンス変数に代入されます。
アーカイバにもキーなしデータを扱うNSArchiverとキー付きデータを扱うNSKeyedArchiverがあります。そのうち本書ではNSKeyedArchiverだけを説明しています。詳しくは10章を見てください。
Encodeメソッドの第1引数にはインスタンス変数を指定します。そして第2引数には各インスタンス変数を識別するためのキーを文字列で指定します。この文字列は英文字でなければなりません。
キーについての注意 ここでいうキーとはあくまでもこのencodeメソッドで使うキーのことを指しています。このキーについて何か命名規則があるのか一応調べましたが、明確な規約を見つけることはできませんでした。そこでencodeメソッドとdecodeメソッドで使うキーの命名規則を自分なりに次のように決めてみました。 クラス名+インスタン変数名 当然これに従う必要はありません。実際にこのSupplement Bのサンプルコードもこの規約からはずれています。しかしインスタン変数が増えてきた場合にはこの命名方法に従うと取りあえずencodeメソッドおよびdecodeメソッドだけに限ればキーが衝突する(同じキーになる)ことはなくなります。
もう一度Model.mに実装したエンコードメソッドを示します。
[encoder encodeObject:string
forKey:@"MyClipString"];
encodeObject:の引数はインスタンス変数のstringです。stringインスタンス変数はNSTextStorage型オブジェクトです。しがってNSCoderのエンコードメソッドもencodeObject: forKey: を選んでいます。
□デコードメソッド
-
(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
string = [decoder decodeObjectForKey:@"todoString"];
}
return self;
}
コード説明に入る前にまずはCoder Class
ReferenceのDecoding Dataグループをみてみましょう。
図:NSCoder Class Reference 2
多くのdecodeメソッドが並んでいると思いますが今回も
decodeデータ型名ForKey:
となっているキー付きのメソッドを使用します。Encodeで示したメソッドと対比させると次のようになります
decodeObjectForKey:
decodeDoubleForKey:
decodeFloatForKey:
decodeInt:ForKey:
リファレンスでdecodeObjectForKey:をクリックしてみてください。シグネチャは次のようになっています。
- (id)decodeObjectForKey:(NSString
*)key
戻り値としてすべてのオブジェクトのポインタを表すid型が指定されています。stringインスタン変数はNSTextStorage型のオブジェクトを格納することになっています。
以上でデコードメソッドの
string = [decoder decodeObjectForKey:@"MyClipString"];
の意味はお分かりいただけたと思います。ここで説明しなければいけないことがあるとすればメッセージ式のレシーバとなっているdecoderのことでしょう。これは
- (id)initWithCoder:(NSCoder
*)decoder
というNSCodingプロトコルメソッドの引数として受け取った変数です。変数の中にはNSCoderクラスのインスタンスのポインタが格納されています。つまりNSCoderのインスタンスにdecodeObjectForKeyというNSCoderのインスタンスメソッド(先頭が – で始まっています)を送っています。その際にMyClipStringというキーを指定しています。そしてその結果の戻り値として元のNSTextStorage型に復元されたテキストビューに表示するデータがstringインスタン変数に代入されます。
以上でインスタン変数に格納されているデータのエンコードとデコードは無事機能することになりました。しかしinitWithCoderというNSCodingプロトコルのデコードメソッドについてはさらに説明しなければならないことがあります。
残りわずか! 頑張りましょう
なぜ「プロトコル」を本文から外して補足資料に回したのか徐々に分かってきていただけているのではないかと思います。ターゲットアクションやデリゲートは実際に使う頻度が高いです。同じくプロトコルも実際には使う頻度は高いのですがほとんどの場合はすでにクラスに組み込まれています。自分の作成したクラスに自分でプロトコルを組み込まなければならない場面に出会わないかぎりおそらく真剣に身に付くことはないだろうと思い一旦本文から外すことにいたしました。しかし決してみなさんを責めているわけではありません。たぶんほとんどのことが机上の作業だけでは身に付かないだろうと思います。とは言ってもプログラミングは机上の作業ではあるのですが :-) 残りは1項目だけです。頑張ってください。
4. イニシャライザ
Objective-CにはC++やJavaでいわれるところのコンストラクタはありません。といっても本書はC言語を理解されているかたを対象読者としていますがほかのOOPを理解されていることは前提としていませんのでコンストラクタについて簡単に説明いたします。
オブジェクトの生成(オブジェクトのインスタンス化)はメモリ割当
(allocation)と初期化 (initialization)の2段階をふまえて行われるのはどのOOPでも同じです。しかしC++やJavaなどではメモリ割当は処理系が自動でやってくれます。プログラマが記述しなければならないのはコンストラクタと呼ばれる初期化メソッドだけになります。
これに対してObjective-Cでは
[クラス名 alloc] init];
という式でインスタンスの生成を行うことはすでにご存知のことだと思います。ようするにObjective-CではほかのOOPとは違いメモリ割当のallocを処理系が自動でやってくれません。逆に言えば、ほかのOOPでは「メモリ割当は済んでいるが初期化はまだしていない」という状態のインスタンスが作れないのはもったいないような気もします(ほかのOOPに詳しいわけでもないのでもしかすると作れるのかもしれませんが…)。しかし一般的にはObjective-Cでも前述の式のようにallocとinitは同時に使うように言われています。ようするに初期化まで含めてインスタンスの生成とみなされているということになります。
●allocだけでインスタンスを使ってみる
横道にそれますが、こういう話しが出てくるとObjective-Cが本当にinitなしでインスタンスを作れるか試してみたくなります。さっそく試してみましょう :-)
図:インスタンス生成時にinitしなかった場合
プロジェクトはCommandLineのFoundationつまりObjective-Cのコマンドラインツールとして「allocText」というプロジェクト名で作成しています。コードは次のようになります。
□allocTest.m
#import
<Foundation/Foundation.h>
@interface
AllocTest : NSObject
{
int intValue;
float floatValue;
NSString *string;
}
- (int)intValue;
- (float)floatValue;
-
(id)string;
@end
@implementation
AllocTest
- (int)intValue
{
return intValue;
}
- (float)floatValue
{
return floatValue;
}
-
(id)string
{
return string;
}
@end
int main
(int argc, const char * argv[])
{
NSAutoreleasePool * pool =
[[NSAutoreleasePool alloc] init];
AllocTest *allocTest = [AllocTest alloc];
NSLog(@"intValue
= %d floatValue = %f string = %@", [allocTest intValue], [allocTest floatValue], [allocTest string]);
[pool drain];
return 0;
}
インスタンスを生成しているのはallocTest.mのmain関数の中のコードです。
AllocTest *allocTest
= [AllocTest alloc];
ご覧のとおり init メソッドは使っていませんが、エラーや警告もなく「問題なく完了しました」というコンパイラからの報告も表示されています。コンソールウィンドウでの結果を見ると次のようになっています。
intValue = 0 floatValue = 0.000000 string = (null)
整数値は0、実数値は0.000000 NSStringの値は存在しないことを表すnullとして表現されています。allocメソッドではメモリにインスタン変数を確保したときに、そこに0という値を書き込みます。その値がそれぞれの型で正しく表現されています。NSStringがどういう表現になるのかなと少し楽しみだったのですが、予想範囲内のnullでした(0という値はnullという意味としても使われます)。またこの例では表示していませんがリファレンスカウンタも正常に1になります。
このサンプルでは未初期化の変数の値がすべて0になっています。しかし未初期化の変数がどういう値になるかは処理系に任されています。今回のように0に揃えてくれるほうがむしろ処理系のサービスと言えるでしょう。実際にいろいろな値が入っている場合が多く、そのような値のことを不定値もしくはゴミ値と呼びます。
このようにObjecitive-Cではinitしなくても問題なくインスタンスを使い始めることができます。実際にBrad J.
Cox氏のObjective-Cではallocだけでインスタンスを使い始める例が結構出ていると思います。しかしAppleが(もしかするとNeXTが)Objecitive-Cの権利を買い取ったのち、「allocだけでインスタンスを使い始めるのはマズイんじゃないの」という話しになったのではないかと思います。
なお、Brad
J. Cox氏の頃からinitメソッドは存在しており、allocと同時にinitするという手法も当然のように使われていたこということも合わせて認識しておいてください。
●指定イニシャライザ
すっかり横道にそれてしまいましたが、ここからが本題になりますここからは本題に戻ります。コンストラクタは状況に合わせて複数持つことができます。状況とはインスタンス変数の値をデフォルトのコンストラクの内部で固定されている値で初期化するのではなく、引数として渡した自由な値で初期化したい場合です。こういう場合にはデフォルトのコンストラク以外に新しいコンストラクタを作成して対応することができます。
※インスタン変数は、C++ではメンバ変数、Javaではフィールドと呼ばれます
Objective-Cでも同じ理由で初期化メソッドをinit以外にも複数持つことができます。またObjective-Cでは初期化メソッドは必ずinitの文字から始まることになっています。つまりinit○○○というメソッド名になります。そしてObjective-Cでは初期化メソッドが複数ある場合そのうちのひとつを指定イニシャライザ(designated initializer)にしなければなりません。
ここからの話しは、実際のコードを示したほうが早いでしょう。MyClipにインスタンス変数が2つあったと仮定してください。型は何型であっても良いのですが、いったnともにNSString型であるとしておきます。そしてそれぞれのインスタン変数名はstringとtitleといことにしておきます。
- (id)initWithString:(NSString *)aString title:(NSString *)aTitle
{
self = [super init];
if (self)
{
string = aString;
title = aTitle;
}
return self;
}
- (id)initWithString:(NSString *)aString
{
self = [self initWithString:aString
title:@"New title"];
return self;
}
-
(id)init
{
self = [self initWithString:@"Enter
note here." title:@"New title"];
return self;
}
コードをじっくりと見てください。3つの初期化イニシャライザメソッドが定義されていますが、2番目と3番目のメソッドは1番目のメソッドを呼び出す形になっていいます。このように引数の個数によって複数のイニシャライザを作ることはできますが、すべてのイニシャライザはあるひとつのシニシャライザを呼び出すように定義しなければなりません。このようにすべてのイニシャライザから呼び出されるイニシャライザを指定イニシャライザと呼びます。そしてスーパークラスのイニシャライザを呼び出せるのは指定イニシャライザだけにしなければなりません。また指定イニシャライザは特別な理由のない限り上記の例のように引数の個数が最も多いものする慣習になっています。
なお、NSObjectの指定イニシャライザはinitです。ルートクラスのNSObject以下のサブクラスでも変更されない限りやはり指定イニシャライザはinitのままになっています。
●例外的存在のinitWithCoder
以上が指定イニシャライザの概要です。ところがこの指定イニシャライザの規則にあてはまらないイニシャライザが存在します。それがNSCodingプロトコルで宣言されているデコードメソッドのinitWithCoderです。
-
(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
string = [decoder decodeObjectForKey:@"todoString"];
}
return self;
}
このコードの中では指定イニシャライザを呼び出すコードは見あたりません。そしてまた [super init] メッセージ式を実行しています。通常ならこのメソッドが指定イニシャライザであってもおかしくはありません。
しかし実際には、スーパクラスのNSObjectから継承したinitメソッドはinitWithCoderを呼び出すようには上書きしていません。そしてinitメソッドからもスーパクラスのinitメソッドが呼び出せるようになったままです。しておきます。つまりinitメソッドは何も変更していないというです。
継承したメソッドは上書きをする場合をのぞき、実際のコードとして私達の目に見えることはありません。しかし継承したinitメソッドは確実に存在しています。そして上書きで変更しない限りスーパークラスのinitメソッドを呼び出すことになっています。
またstringとtitleとう2つのインスタン変数を持っているオブジェクトの例のように複数のイニシャライザを作り、initメソッドも含めて呼び出し関係のグループを作り指定イニシャライザを作った場合でもinitWithCoderはそのグループに含めてはいけません。そして指定イニシャライザからだけでなくinitWithCoderからもスーパークラスの指定イニシャライザを呼び出せるようにしておかなければなりません。
以上のようにinitWithCoderは通常のイニシャライザからは完全に外れた存在になっています。しかし考えてみればこれは当たりまえのことかもしれません。initWithCoderはinitとう名前こそついていますが、ほかの初期化メソッドとは趣きを異にしています(少し役割が違います)。ほかの初期化メソッドは最初から決められていた値で初期化するか、ユーザが入力した値で初期化するか、という方法が決められている予定調和的メソッドです。それに対してinitWithCoderの目的はデータの初期化ではなくデータを元の状態に戻す復元が目的です。他の初期化メソッドとは一線を引いて別格扱いされて当然のことでしょう。
これでNSCoderについての説明は終わります。しかし最後にひとつ。今回のinitWithCoderのサンプルコードでは
self = [super init];
となっていますが、それはスーパークラスのNSObjectがNSCodingプロトコルを採用していないからです。もしスーパークラスがNSCodingプロトコルを採用していればこのコードは
self = [super initWithCoder];
となります。このことからもinitWithCoderがほかのイニシャライザとは別ルートのメソッドとして扱われていることがよく分かると思います。
お疲れ様でした。これで補足資料B「プロトコル」の説明を終わります。本文中でも説明いたしましたが、この補足資料で説明したことはオブジェクトに自らをエンコードする機能とデコードする機能を搭載しただけです。実際にそれを実行するのはアーカイバというオブジェクトに任されます。アーカイブについては10章で説明されています。ご覧になってください。
This site is available in Safari and Snow Leopard. | (c) viva Cocoa 2006 - 2010 |