【コラム】

ダイナミックObjective-C

50 デザインパターンをObjective-Cで - Singleton (1)

    木下誠  [2006/09/27]

    前回の予告通り、今回からデザインパターンをObjective-Cで実装してみる。

    取り上げるパターンの順番だが、テキストとして使用する「オブジェクト指向における再利用のためのデザインパターン」(以下GoF本)とは少し変更する。まず生成に関するパターンから始めるのは同じだが、その中のSingletonを最初に取り上げよう。

    Singletonとは

    Singletonは、インスタンスを1つしか生成しないクラスのためのデザインパターンだ。主な用途としては、ファイルマネージャや、ウィンドウマネージャなど、対象がただ1つしかないものが挙げられる。

    Singletonクラスのインスタンスを取得する場合、通常のallocinitといったメソッド(C++でのnewに相当)は、使わない。その代わりに、インスタンス取得のためのメソッドを提供することになる。作成された、ただ一つのインスタンスは、Singletonクラスで管理される。

    Objective-Cでの実装

    さて、このようなパターンをObjective-Cで実装してみよう。まず、インスタンス取得のためのメソッドは、クラスメソッドを使う。次に、作成したインスタンスだが、本来ならクラス変数として管理したいところだが、Objective-Cにはクラス変数はない。そこでよく使われるのが、クラスの実装ファイルにstatic変数を宣言する方法だ。C言語で使われる単純な方法だが、機能としては十分だ。

    Singletonという名前のクラスを定義しよう。インスタンスを得るために、instanceという名前のクラスメソッドを持つ。

    List 1. Singleton.h

    @interface Singleton : NSObject
    + (id)instance;
    @end

    実装は次のようになる。instanceメソッドの中でstaticのSingleton変数を宣言し、最初に呼び出されときにだけ、インスタンスを作成する。

    List 2. Singleton.m (1)

    @implementation Singleton

    + (id)instance
    {
        static Singleton* _instance = nil;
        if (!_instance) {
            _instance = [[Singleton alloc] init];
        }
        return _instance;
    }

    @end

    実際に使うときは、次のようなコードになるだろう。

    List 3. 使用例

        Singleton* singleton = [Singleton instance];

    これで、ただ1つのSingletonインスタンスを使い回すことができる。基本的には、こういうスタイルになると思う。もう少し、突っ込んだ議論をしてみよう。

    インスタンス作成のタイミング

    List 2では、最初にinstanceメソッドを呼び出したときに、インスタンスを作成した。このタイミングでの作成は、複数スレッドからこのメソッドが呼ばれる場合、問題が起こることがある。インスタンス作成のチェックを、_insntanceがnilかどうかで行っているので、スレッドの動作によっては、2つのインスタンスが作成されてしまう恐れがあるのだ。

    これを避けるには、スレッドのロックを行うようにすればいい。Objective-Cではスレッド同期のための文法は提供されていないので、Cocoaやpthreadなどのライブラリを使うことになるだろう。

    それ以外に、インスタンス作成のタイミングをもっと早めるという方法もある。例えば、クラスがランタイムに読み込まれるときに呼ばれる、loadメソッドを使えばいい。List 2を次のように変更することもできる。

    List 4. Singleton.m (2)

    static Singleton* _instance = nil;

    + (void)load
    {
        if (!_instance) {
            _instance = [[Singleton alloc] init];
        }
    }

    + (id)instance
    {
        return _instance;
    }

    この場合、instanceメソッドを呼び出したときには、すでにインスタンスは作成されていることになる。ただし、この実装にすると、次で説明するサブクラスへの対処が難しくなる。

    Singletonのサブクラス

    先ほどのSingletonクラスを、NewSingletonというサブクラスで拡張することを考えてみよう。このとき、インスタンスは、SingletonNewSingletonのどちらか1つだけを作るものとする。さて、どうすればよいか。

    問題は、instanceメソッドが呼ばれたときに、どちらのクラスから呼ばれたのか? という点にある。Singletonクラスから呼ばれたののならばSingletonのインスタンスを作ればいいし、NewSingletonクラスならばそのインスタンスだ。

    Objective-Cでは、クラスオブジェクトを使うことで解決できる。instanceはクラスメソッドなので、その内部ではself変数はクラスオブジェクトになる。これを使って、インスタンスを作成すればいい。

    List 2を次のように変更しよう。

    List 5. Singleton.m (3)

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

    [Singleton alloc]としていたところを、[self alloc]と変更した。このようにすれば、[Singleton instance]を呼んだときはSingletonのインスタンスが作成され、[NewSingleton insntace]ならばNewSingletonのインスタンスになる。

    GoF本では、インスタンスを作り分けるために、環境変数に名前を設定して、そこから判別する方法が紹介されている。

    List 6. GoFより引用

    MazeFactory* MazeFactory::Instance() {
        if (_instance == 0) {
            const char* mazeStyle = getenv("MAZESTYLE");
            
            if (strcmp(mazeStyle, "bombed") == 0) {
                _instance = new BombedMazeFactory();
            } else if (strcmp(mazeStryle, "enchated") == 0) {
                _instance = new EnchantedMazeFactory;
            }
            ...
        }
        return _instance;
    }

    この方法では、作成するインスタンスの名前をあらかじめ設定しておく必要がある。Objective-Cでは、クラスメソッドからクラスのランタイム情報にアクセスできるので、追加の情報がなくともサブクラス化に対応できることが分かると思う。

    次回は、Cocoaフレームワークの中に見られるSingletonパターンを紹介しよう。

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

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