【コラム】

ダイナミックObjective-C

15 クラスとは何か(2) - クラス情報に直接アクセスする

    木下誠  [2005/11/16]

    前回は、クラスを表す型Classが、obc_class構造体であることを説明した。今回は、実際にこの構造体の中身を触ってみることにしよう。Objective-Cというプログラミング言語は、ランタイムの記述言語であるC言語を完全に含んでいるため、簡単に自分の中身にアクセスすることができる。

    Class型を取得する

    まず、Class型を取り出すことから始めよう。ランタイムにまで目を向けたおかげで、Class型もいろいろな方法で取り出すことができるようになる。

    Objective-cでは、これまで説明してきたように、Classメソッドを使ってClass型を取得する。Classメソッドは、ルートクラスであるNSObjectで定義されているメソッドであり、すべてのインスタンスオブジェクトからClass型を取り出すことができる。また、クラスオブジェクトから取り出す、クラスメソッドとしてのClassもある。

    また、Cocoaが提供している関数を使う方法もある。文字列を指定してClassを取得する、NSClassFromStringという関数だ。文字列を表すクラスであるNSStringを使って、クラス名を指定して取り出す。この2つが、通常使う方法だ。

    これらに加えて、ランタイムAPIを使うこともできる。objc_getClassとobjc_lookUpClassという関数を使う方法だ。これらは、C文字列で指定されたクラス名に対するClass型を返す。2つの関数の違いは、指定したクラスが見つからなかったときの動作だ。objc_getClassでは登録されたハンドラを呼び出し、objc_lookUpClassはNULLを返す。これらは、objc/objc-runtime.hで宣言されている。

    実際の使い方を、ソースコードで示そう。NSStringクラスに対応するClass型を取り出す例だ。

    Class cls;

    // Objective-CのインスタンスからClassを取得する
    // stringはインスタンス化されているものとする
    NSString* string;
    cls = [string class];

    // Objective-CのクラスからClassを取得する
    cls = [NSString class];

    // Cocoaの関数を使って、文字列からClassを取得する
    cls = NSClassFrmString(@"NSString");

    // ランタイムAPIを使って、C文字列からClassを取得する
    cls = objc_getClass("NSString");
    cls = objc_lookUpClass("NSString");

    このように、Objective-Cのメソッド、ユーティリティ関数、ランタイムAPIと、異なったレベルで実現されている。これらを混ぜ込んで使えるのが、面白いところだ。もちろんいずれも実行結果は同じになる。

    クラス名を取り出す

    では、Class型に直接アクセスしてみよう。前回説明したように、Class型とはobjc_class構造体のポインタのことである。そしてobjc_classにはnameという、クラスの名前を示すフィールドがある。これを使って、クラス名を表示してみよう。

    // NSStringのClassを取得する
    Class cls;
    cls = [NSString class];

    // クラス名を、printfで表示する
    printf("class name %s¥n", ((struct objc_class*)cls)->name);

    このように、直接キャストして使ってしまえばいい。Objective-Cのクラスとは、結局のところC言語の構造体であり、それ以上でもそれ以下でもない、ということが実感できるかと思う。他のフィールドにも、同様にアクセスすることができる。

    すべてのクラス名を表示する

    クラス名を指定してClassを取り出すAPIがあるということは、ランタイムのどこかでクラス情報をまとめ管理していることを意味する。実際、このすべてのクラス情報に、直接アクセスするためのAPIもある。objc_getClassListだ。

    int objc_getClassList(Class* buffer, int bufferLen);

    この関数は、ランタイムにあるクラス情報を、引数のbufferにコピーする。登録されているクラスの数を調べることもでき、bufferにNULLを渡すと、クラスの数を返す。

    これを使って、現在のランタイムにあるすべてのクラス名を表示してみよう。

    #import <objc/objc-class.h>

    // すべてのクラス名を表示します
    void showClassList()
    {
        // 登録されているクラスの数を取得します
        int numClasses;
        numClasses = objc_getClassList(NULL, 0);
        if (numClasses == 0) return;
        
        // すべてのClassを取得します
        Class* classes;
        classes = malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        
        // クラス名を表示します
        int i;
        for (i = 0; i < numClasses; i++) {
            Class cls;
            cls = classes[i];
            printf("%s\n", ((struct objc_class*)cls)->name);
        }
        
        free(classes);
    }

    実行すると、クラス名の一覧が表示される。面白いことに、Cocoaだけでなく、リンクされているすべてのフレームワークのクラス名を表示することができる。クラス名から、フレームワークの隠れた動作をいろいろ推測することができて楽しい。

    このように、Class型の内部構造と、ランタイムAPIを使うことで、Objective-Cの言語仕様には含まれない情報にアクセスすることができるようになる。いや、むしろこれらも含めてObjective-Cの機能というべきか。

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

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