Objective-Cにおけるシングルトンのお話

どうもこんばんは、south37です。前回に引き続き今回もObjective-Cネタです。 iOSアプリ開発をしてるとシングルトンが活躍するケースがよくある(らしい)のですが、シングルトン生成のコードがパッと理解出来なくて悶々としたので、ここにメモとして残しておきます。

シングルトン

シングルトンは、あるクラスのインスタンスが単一である事を保証するデザインパターンです。iOSアプリ開発における利用例としては、例えばAFNetWorkingというライブラリのAFNetworkActivityIndicatorManagerというクラスにおいて、シングルトンが使われています。

以下が、実際の実装です。

1  // "AFNetworkActivityIndicatorManager.m"
2  + (instancetype)sharedManager {
3      static AFNetworkActivityIndicatorManager *_sharedManager = nil;
4      static dispatch_once_t oncePredicate;
5      dispatch_once(&oncePredicate, ^{
6          _sharedManager = [[self alloc] init];
7      });
8  
9      return _sharedManager;
10 }

解説は後でしますが、単一のインスタンスを返すようなクラスメソッドsharedManagerを定義しているのがポイントです。[AFNetworkActivityIndicatorManager sharedManager]のように呼び出す事で使用され、いつsharedManagerを呼び出しても同一のインスタンスが返されます(シングルトン)。

シングルトンは、上記の例のように~Managerみたいな名前のクラスに使われている印象です。確かに、Managerがいっぱいいても何か変な気はします。

シングルトンの実装コードの解説

さて、上記コードのうち、自分が疑問だったのはstatic指定子がついた変数の宣言の部分でした。特に、_sharedManagerメソッド呼び出しの度にnilに初期化される様に思えて、全然シングルトンにならないじゃないかとか思ってました。

これは、もちろん僕の勘違いでした。

static指定子の変わった性質

結論としては、static指定子のついた変数の宣言&初期化は一度しか実行されません。static変数は一度生成されるとずっとメモリに残る(メソッド内で寿命が閉じていない)ため、次にsharedManagerメソッドを呼び出した時にはstatic変数の宣言のコードは無視されるのです。

[参考: The Static Keyword] http://rypress.com/tutorials/objective-c/functions.html

ちなみに、メソッド内でstatic指定子をつけて宣言された変数はstatic local variableと呼ばれ、スコープは宣言したメソッドで閉じているものの寿命は長いという性質を持っています。その意味で、クラス変数の代わりとして使われるstatic variableとは役割が違うようです。

[参考: Static local variables] http://en.wikipedia.org/wiki/Local_variable

という訳で、_sharedManagerについては最初にnilで初期化されてるだけだという事が分かりました。

次は、dispatch_once_t型とかdispatch_once関数とかに着目してみましょう。

dispatch_once: 一度しか実行されない事を保証する

実は、

  static dispatch_once_t oncePredicate;
  dispatch_once(&oncePredicate, ^{
      // 何かしら一度しか実行しない処理
  });

というのは一度しか実行されない処理を書く為のイディオムとなっています。dispatch_once_t型というのがlong型(int型とほぼ同じ)の別名で、onePredicate変数が初期化時に0, 一度dispatch_onceを実行すると-1になる事でdispatch_onceが実行されたかどうかを識別する為のフラグの役割を果たしています。

これは、NSLogしてみると分かり易いです。確かに、dispatch_once実行後に-1になっている事が分かります。

   static dispatch_once_t oncePredicate;
   NSLog(@"%d", oncePredicate);
   dispatch_once(&oncePredicate, ^{
       // 何かしら一度しか実行しない処理
   });
   NSLog(@"%d", oncePredicate);
// コンソール出力
// 2014-07-13 02:43:00.386 Hello[30133:60b] 0
// 2014-07-13 02:43:00.388 Hello[30133:60b] -1

[参考: Grand Central Dispatch (GCD) Reference] https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_once

このイディオムを使えば、複数スレッドからの呼び出しに対しても一度しか処理が行われない事が保証されるらしいです。とっても素晴らしいですね!!

まとめ

シングルトンを生成する為のお決まりのパターンが分かりました。昔はシングルトンのインスタンスnilかどうか(初期化済みかどうか)でif分岐するという方法がとられていた様ですが、スレッドセーフでは無かった為、現在ではdispatch_onceを使う方がオススメのようです。

おまけ: C言語とかでのstatic

http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10102644985

とかにも載ってる通り、static宣言が一度しか実行されないというのはC系言語ではむしろ普通の振るまいのようですね。普段LLばっか触ってるんで完全に無知でした。。。反省。。。