【コラム】

ダイナミックObjective-C

8 カテゴリ - 動的なメソッドの追加によるクラスの拡張

    木下誠  [2005/09/29]

    今回からは話を変えて、Objective-Cの別の側面を紹介しよう。メソッドの宣言、実装にまつわる話だ。まずは、カテゴリから始めよう。

    カテゴリによるクラスの分割

    Objective-Cには、カテゴリと呼ばれる機能がある。クラスが持つメソッドを、名前の通りカテゴリごとに分類するための機能だ。これを使うことで、大きなクラスでもコーディングしやすいように分割する、といったことができる。

    例を示そう。Objective-Cでは、メソッドの定義は@interface ClassNameという形で行うが、メソッドをカテゴリに分類するときは、@interface ClassName (Category)という文法を使う。次の例では、Documentというクラスを宣言し、そのクラスにPersistenceというカテゴリを追加している。このカテゴリは、書類の読み書きに関するメソッドを担当するようにしてみた。

    // Documentクラスの宣言
    @interface Document : NSObject
    {
        // インスタンス変数
        NSString* title;
    }
    - (NSString*)title;
    - (void)setTitle:(NSString*)title;
    @end

    // DocumentクラスのPersistenceカテゴリ
    @interface Document (Persistence)
    - (void)writeToFile:(NSString*)path;
    - (void)readFromFile:(NSString*)path;
    @end

    カテゴリで実装するメソッドは、通常のメソッドと何ら変わりはなく、インスタンス変数にも自由にアクセスできる。実際、カテゴリを使わずに、1つのクラスにまとめてしまっても何の違いもない。ただ、カテゴリを定義する際、カテゴリ側で独自のインスタンス変数を宣言することはできない。インスタンス変数を追加したい場合は、常にもとの宣言を変更する必要がある。

    カテゴリの直接的な利点は、大きいクラスの実装を、複数のファイルに分割できる点だ。通常、Objective-Cでは1つのクラスの実装を1つのファイルに書く。カテゴリを使うと、カテゴリ毎に1つのファイルを割り当てることができるので、ソースコードが大きくなりすぎたときなどにこれを利用することができる。

    カテゴリなしとカテゴリありの場合

    カテゴリの実現方法

    言語仕様の面から見ると、カテゴリがただの便利な分割機能にしか見えないかもしれない。だが、実装面に目を移すと、非常に面白いものが見えてくる。

    実は、Objective-Cのランタイムでは、動的に既存のクラスにメソッドを追加していくことができる。極端な話、オブジェクト指向で「クラス」と呼ばれているものは、インスタンス変数とメソッドの宣言を集めたものだ。ならば、実行中に、ここに直接メソッドを追加していく機能があってもいいだろう、というわけだ。

    カテゴリは、この機能を使っている。クラスをカテゴリを使って分割すると、それぞれ別のオブジェクトとしてコンパイルされる。そしてこれらがランタイムに読み込まれた時点で、もとのクラスにカテゴリで宣言されているメソッドが追加されていく。

    メソッドの追加によるクラスの拡張

    ここで、「既存のクラスにメソッドを追加」というところに注目してほしい。つまり、これを使うと、いまあるクラスの機能を拡張することができるのだ。自分で定義したクラスに限らず、Cocoaが提供するクラスも含めて、だ。

    たとえば、Cocoaで文字列機能を提供するNSStringというクラスがある。このNSStringの機能を、カテゴリでメソッドを追加することで、拡張することができる。例えば、文字列から改行コードを除去するための機能を追加してみよう。

    // NSStringの新たなカテゴリの宣言
    @interface NSString (TrimNewLine)
    - (BOOL)hasNewLineCharacters;
    - (NSString*)stringByTrimmingNewLineCharacters;
    @end

    @implementation NSString (TrimNewLine)
    - (BOOL)hasNewLineCharacters
    {
        // 改行コードが含まれていたら、YESを返す
        ……
    }

    - (NSString*)stringByTrimmingNewLineCharacters
    {
        // 改行コードを除去した文字列を返す
        ……
    }
    @end

    これで、NSStringを拡張できる。この拡張されたメソッドは、すべてのNSStringクラスのインスタンスに対して呼び出すことができる。自分で作成したインスタンスだけでなく、システム側で作られたものも含めて、である。

    オブジェクト指向では、クラス拡張の手段は、サブクラス化が一般的だ。Objective-Cではそれに加えて、今回説明したように、カテゴリによるメソッドの追加という手段もよく使われる。サブクラス化による拡張では、メソッド呼び出しのオーバーヘッドが増えたり、既存のインスタンスを拡張することはできないという問題がある。カテゴリは、手軽に使え、かつ、これらの問題を補う利点を持っている。

    この利点は、クラスへのメソッドの動的な追加が行える、という点によっているのだ。

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

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