【コラム】

ダイナミックObjective-C

51 デザインパターンをObjective-Cで - Singleton (2)

    木下誠  [2006/10/04]

    Singletonパターンの話を続けよう。前回の最後で、Cocoaフレームワークに見られるSingletonの話をすると書いたが、前回の記事について読者から指摘をもらったり、書き漏らしたトピックがあったので、もう一回Singletonの実装の話をすることにする。

    スレッドの排他制御

    まずは、Singletonのインスタンスを取得するメソッドを、複数スレッドから呼ばれたときに排他制御することについて。

    前回、「Objective-Cではスレッド同期のための文法は提供されていない」と書いてしまったが、これは誤りだ。昔のObjective-Cにはなかったが、現在では@synchronizedというキーワードが追加されている。これを使うと、ランタイムレベルでの排他制御を行うことができる。GCC 3.3以降、Mac OS Xだと10.3以降から使用可能だ。

    この文法に関する詳しい説明は、「Objective-Cプログラミング言語:例外処理とスレッド同期」を参考にしてほしい。リンク先のページにも書いてあるが、この機能を有効にするには、コンパイラで-fobjc-exceptionsスイッチを指定する必要がある。

    さて、@synchronizedを使って前回のソースコードを修正すると、次のようになる。

    List 1. Singleton.m (1)

    + (id)instance
    {
        static id _instance = nil;
        @synchronized(self) {
            if (!_instance) {
                _instance = [[self alloc] init];
            }
        }
        return _instance;
    }

    これで、複数スレッドからのアクセスにも耐えうるメソッドになる。

    インスタンスが1つであることの保証

    Singletonパターンは、1つの共有するインスタンスを提供するというほかに、そのクラスのインスタンスが1つしか存在しない事を保証するという性質も含む。この性質も実装してみよう。いままでのコードだと、[[Singleton alloc] init]と書いてしまえば、新しいインスタンスが確保されてしまう。これを防ぐのだ。

    C++やJavaなどでは、コンストラクタをprivateにすることで、これに対応する。Objective-Cの場合、メソッドの可視性を設定することはできないので、この方法は使えない。仮にメソッドがprivateかどうかコンパイルの時点でチェックできたとしても、objc_msgSendを使えばメッセージとして投げてしまうので、あまり意味はないだろう。

    そこで、インスタンスのためのメモリ領域を確保するallocWithZone:メソッドを上書きしてしまう。これにより、1つ目のインスタンスは作成し、それ以降は作成しない、という制御ができることになる。

    ついでに、オブジェクトの参照カウンタを制御するretain/release/autoreleaseメソッドや、コピーを行うcopyWithZone:メソッドもつぶしてしまおう。これでインスタンスの複製や削除が行われないことも保証される。

    List 2. Singleton.m (2)

    static id _instance = nil;

    + (id)instance
    {
        @synchronized(self) {
            if (!_instance) {
                [[self alloc] init];
            }
        }
        return _instance;
    }

    + (id)allocWithZone:(NSZone*)zone
    {
        @synchronized(self) {
            if (!_instance) {
                _instance = [super allocWithZone:zone];
                return _instance;
            }
        }
        return nil;
    }

    - (id)copyWithZone:(NSZone*)zone
    {
        return self;
    }

    - (id)retain
    {
        return self;
    }

    - (unsigned)retainCount
    {
        return UINT_MAX;
    }

    - (void)release
    {
    }

    - (id)autorelease
    {
        return self;
    }

    以上のコードは、「Cocoa Fundamental Guide: シングルトンインスタンスの作成」を参考にした。

    あえてインスタンスを削除

    デザインとしてSingletonだが、そのインスタンスを必要に応じて削除したい場合もあるだろう。つまり、インスタンスは常に1つである。そして、そのインスタンスを必要なときに作成し、使い終わったら削除する、というパターンだ。この場合、削除を行うメソッドを追加することになる。

    List 2に、削除のためのメソッドdeleteInstanceを付け加えてみよう。releaseメソッドも変更することになる。

    List 3. Singleton.m (List 2に追加)

    static BOOL _willDelete = NO;

    + (void)deleteInstance
    {
        if (_instance) {
            @synchronized(_instance) {
                _willDelete = YES;
                [_instance release];
                _instance = nil;
                _willDelete = NO;
            }
        }
    }

    - (void)release
    {
        @synchronized(self) {
            if (_willDelete) {
                [super release];
            }
        }
    }

    - (void)dealloc
    {
        // このクラスの削除処理を記述
        
        [super dealloc];
    }

    static変数_willDeleteを付け加えてみた。これにより、deleteInstanceが呼ばれたときにだけ、インスタンスが削除されることになる。

    次回こそは、Cocoaでの実例の紹介を行おう。

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

        マイナビニュースマガジン