Objective-Cのシングルトンって、昔はcopyWithZoneとかもあって、無駄に面倒だったような記憶があるんだけど、いつの間にか簡単になったんだっけ……?と思ってたので、ちょっと整理してみました。
ARCとGCDが出る前からシングルトンを実装してた人にはちょっと懐かしい話かもしれません。
Singletonとは
改めて説明するまでもないですが、シングルトンとはプロセス内のどこからよんでも同じオブジェクトにアクセスできるようにするデザインパターンです。 どのプログラミング言語においても重要なデザインパターンです。
iOSのシステムフレークワークでもよく使われており、
などはおなじみですよね。 上のメソッドは何回呼んでも同じオブジェクトがかえるようになっているので、いつでも同じオブジェクトに対して処理をすることができます。
自分で実装しなくても、ほとんどの人はシングルトンパターンを使っていると思いますが、シングルトンパターンを自分で実装することは、初心者から中級者へのステップの一つかなと思います。
Singletonの実装 - 単一のオブジェクトを返す
シングルトンの生成メソッドは、クラスメソッドとして実装します。 メソッド名に決まりはないんですが、一般的には sharedManager とか sharedInstance などが使われます。
Objective-Cにはクラス変数がないので、「単一のオブジェクト」を保持する場所としては、static変数を使います。
まずは、一回目(sharedInstanceがないとき)にだけ初期化を行い、それ以外の場合にはalloc済みのインスタンスを返すようにしてみましょう。
上のメソッドを使うときには、[Heart1 sharedInstance]と呼べば、プログラムのどこからよんでも同じインスタンスにアクセスできるはずですが、まだまだちょっと問題があります。
Singletonの実装 - 初期化処理が一回しか呼ばれないようにする
さきほどのコードではこのメソッドが複数のスレッドからよばれた場合に複数のsharedInstanceが作られてしまう可能性があるので、スレッドからの呼び出しに対してロック処理をかける必要があります。 GCDが出る前によく使われていたロック処理、@synchronizedを使うとこんなコードになります。
なお、@synchronizedを使わなくても、loadやinitializeなどを使って、初期化処理が行われる前にsharedInstanceを作成してしまう方法もありますが、Apple的には推奨ではないみたいですね。
loadを使った場合はこんな感じ
initializeを使った場合
Singletonの実装 - サブクラス化したときに
さて、@synchronizedを使った処理に戻って見てみると、さきほどのコードには一つだけ問題があります。
sharedInstance = [[Heart2 alloc] init];
と書いているので、もしこのクラスをサブクラス化したときに予想通りに動かない可能性があります。 ここは、クラス名ではなくselfを使ってallocを行いましょう。 (クラスメソッドの中では、selfはオブジェクトではなくクラスになります。)
Singletonの実装 - allocされたら?(ARCの場合)
さて、ここでシングルトンの定義をもう一度振り返ってみましょう。 シングルトンは、「プロセス内のどこからよんでも同じオブジェクトにアクセスできるようにするデザインパターン」なので、どこでよばれても同じオブジェクトがかえります。
ただ、上のソースコードでは、
[Heart sharedInstance]
とよべば同じいつでもオブジェクトがかえってきますが、実は
[[Heart1 alloc] init]
とよぶと、毎回違うオブジェクトがかえってきます。
呼び出す側で気をつけて[[Heart1 alloc] init]
を使わないようにする、という考え方もありますが、そこも想定した設計にしておく方がより安全ですよね。
そこで、allocで呼び出されたら必ずnilをかえすようにしてみましょう。
allocWithZone
をオーバーライドし、初回のみallocation処理が実行されるようにします。
copyメソッドを使われる可能性がある場合には、これをいれておいても有効です。
Singletonの実装 - allocされたら?(MRCの場合)
で、話がちょっと戻ってしまうんですが、MRC(ARCを使わない)場合には、allocWithZone に加えてratain/releaseを上書きする必要もあったので、こんなに複雑になっていました。 やっぱりARCになって、いろいろなことがだいぶ楽になりましたよね。
Singletonの実装(完全版) - そして、GCD
ARCが導入されるとretain/releaseの処理を省略でき、さらに@synchronizedのかわりにGCDのdispatch_onceを使えばこの通り。 これで、allocされてもcopyされても安全なシングルトンの実装が完了です。
Singletonの実装(簡易版)
上記の処理からallocWithZoneとcopyWithZoneをのぞいたのがこちら。 たぶんこの実装を使っている人が一番多いと思いますが、この実装の場合には、alloc/copyを呼び出し側で使わない注意が必要です。
まとめ
あらためて振り返ってみると、やっぱりARCとGCDでだいぶ変わりましたよね。 最近は、Objective-Cを書いていても「昔はもっと面倒だったんですよ〜」ということが増えているので、ちょっと老害化してるのかもしれません……。
サンプルコードはこちら