2013年11月13日水曜日

Objective-C でシングルトンパターン (緩め)

Objective-C でシングルトンを実現するのは、ちゃんとやろうとすると、結構手間です。例えば、ARC (Automatic Reference Counting) 無効の場合、次のブログが示しているように、シングルトンインスタンスを取得するメソッドを追加するだけでは話は終わらず、あまり直感的ではない方法で複数のメソッドをオーバーライドしなければなりません。

Objective-C でシングルトンパターン
http://blog.syuhari.jp/archives/2178

ARC 有効となると、書き方も変わってきます。

言語の特性上、呼び出し側で無茶なことができてしまうケースが幾つかあるので、シングルトン実装側だけで完全性を達成しようとするのは難しいと思います。そのため、ある程度シングルトン的な構造にはしておくものの、呼び出し側がひねくれたことをやったら責任は負えないよ、という実装で落ち着くというのも、ケースによっては許容できると思います。

なので、例えば次のようなシングルトンの実装もありではないでしょうか。

// シングルトンインスタンスを保持する変数。
// static を付けているので、C 言語の仕様上、
// 他のソースコードからは参照できない。
static MyClass *_instance;

// +(void)initialize は Objective-C にとって特別。
// クラスの初期化時に一度だけ呼ばれる。
+ (void)initialize
{
    // あるクラスが initialize メソッドを実装していない場合、
    // そのスーパークラスの initialize メソッドが呼ばれて
    // しまうそうなので、同期をとっておく。(今回のケースでは
    // シングルトンクラスのサブクラスを作るというような変な
    // ことでもしない限り、当 initialize メソッドが複数回
    // 呼ばれることは無いが、一般的なコード例として同期)
    @synchronized (self)
    {
        // まだシングルトンインスタンスが作成されていなければ
        if (_instance == nil)
        {
            // シングルトンインスタンスを作る。初期化メソッドは
            // init ではなくて initInternal としておく。
            _instance = [[MyClass alloc] initInternal];
        }
    }
}

// シングルトンインスタンスを取得するためのクラスメソッド。
// sharedInstance というメソッド名はよく使われるので、
// その慣習に従うことにする。
+ (MyClass *)sharedInstance
{
    return _instance;
}

// デフォルトの初期化メソッド。間違って呼ばれないよう、
// 機能しないように実装しておく。
- (id)init
{
    // 呼び出し側が間違えて init メソッドを呼ぶことのないよう、
    // つまり _instance 以外のインスタンスを作ることがないよう、
    // init メソッドは機能しないようにしておく。ここでは、
    // 「そんなメソッド知らない」という例外を投げることにする。
    [self doesNotRecognizeSelector:_cmd];

    return nil;
}

// init のかわりの初期化メソッド。Objective-C の言語仕様上、
// 外部から initInternal を呼ぶことはできてしまうが、わざわざ
// 公開もしていない initInternal を呼んでシングルトンパターンを
// 壊すことをするようなら、それは呼び出し側が悪いので、何が
// 起こっても知ったことではない。
- (id)initInternal
{
    self = [super init];

    return self;
}

// 以下はヘッダファイル内の記述。init メソッドは使用できないと
// 宣言しておく。
- (id)init UNAVAILABLE_ATTRIBUTE;