Objective-C イニシャライザを使いこなす.
イニシャライザの種類
イニシャライザはおおまかに分けて2つの種類があります。最低限の設定だけをデフォルト値で済ませてしまうイニシャライザと、引数をとり、それを用いて細かな設定を行うイニシャライザです。
次のようなAnimalクラスを考えたとします。Animalクラスはインターフェース部で性別を格納するunsigned int型のインスタンス変数”_gender”を定義しており、0が「性別なし」、1が「オス」、2が「メス」を示すとします(通常は状態などを表す変数はenumなどを用いますが)。
実装部の初期化は以下のようなものが考えられます。
/*Animalクラス*/ @implementation -(id) init { if((self = [super init])) { _gender = 0; } return 0; } -(id) initWithGender:(unsigned int)gen { if((self = [self init])) { _gender = gen; } return 0; } @end
”init”がここでは最低限の設定をするイニシャライザと考えていいでしょう。”_gender”にはデフォルト値として0を代入しています。
一方で”initWithGender”もここではイニシャライザです。しかし、このメソッドは引数を持ち、どこかからイニシャライズされる際にはgenを受け取ります。そしてそれを_genderに代入しています。この処理によって、初期化の時点で性別を決定してしまう事ができるのです。
このinitように、デフォルトの値を用いて、最低限の初期化設定を行うイニシャライザを「指定イニシャライザ」と呼び、initWithGenderのように引数などを指定して、細かく設定するイニシャライザを「副次的イニシャライザ」と呼びます。
では、もう少しプログラムを細かく見て行きましょう。
initイニシャライザではif文の中で「super」を使ったinitの呼び出しをしています。これによりスーパークラスのイニシャライザが呼び出されています。
一方でinitWithGenderイニシャライザでは「self」を用いた呼び出しが行われています。これはすなわち、スーパークラスではなく、Animalクラスのinitを呼び出していることになります。
このようにして、副次的イニシャライザは「self」変数を利用して、自分のクラスの指定イニシャライザを呼び出すべきです。そして、指定イニシャライザはスーパークラスの指定イニシャライザを呼び出すべきです。
この規則を守ることによって、継承関係の中で、例え様々なイニシャライザを作ったとしても、下図のように上手くルートクラスまでの初期化が行われるのです。
誤った継承が起こる例
今度は上の規則を守らなかった場合に、プログラムがどのように遷移していくか見てみましょう。
次の図は先程の図と違い、クラスBの指定イニシャライザが”init”、副次的イニシャライザの一つが”initWithGender”に変わっています。この状態の時のクラスCインスタンスの参照を見てみましょう。
クラスCの指定イニシャライザinitは「スーパークラスの指定イニシャライザを呼び出す」という規則が守られておらず、①のようにクラスBのinitWithGenderを呼び出しています。
このように参照されると、クラスB”initWithGender”内の”self”はクラスCのインスタンスのことを指してしまうので、本来起こるべきだった②の呼び出しは行われず、③の呼び出しが行われることになります。
するとまた、クラスCは①の呼び出しを行なってしまい、再帰呼び出しになってしまいます。
このような状態になってしまうと、プログラムは無限ループに入り、強制終了するしかありません。
決して、イニシャライズの際に、スーパークラスの副次的イニシャライザを呼び出してはいけないという規則があるわけではありませんが、定義の方法に注意しなければ、上記のような無限ループが起こりえます。
プログラムを描く際は、コメントなどで、どれがこのクラスの指定イニシャライザなのかわかるように明記しておくようにしましょう。