NSFastEnumerationプロトコル

以前のObjective-Cでは、列挙子を実現するためにNSEnumeratorが使われていた。NSEnumeratorはクラスであり、nextObjectという次のオブジェクトを取得するためのメソッドが定義されていた。

Objective-C 2.0のFast Enumerationでは、この機能を実現するためにNSFastEnumerationというものを使う。これはプロトコルになる。このプロトコルに準拠しているコレクションクラスに対して、例のfor文による列挙が使えるわけだ。ちなみに、NSArray、NSDictionary、NSSet、NSHashTable、NSMapTableといった、主なコレクションクラスで準拠している。

NSFastEnumerationプロトコルの定義は、次のようなものになる。

リスト1

typedef struct {
    unsigned long   state;
    id*             itemsPtr;
    unsigned long*  mutationsPtr;
    unsigned long   extra[5];
} NSFastEnumerationState;

@protocol NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state objects:(id*)stackbuf count:(NSUInteger)len;
@end

まず、NSFastEnuemrationStateという構造体が定義されている。これは、列挙の状態を記述するものになる。そして、countByEnumeratingWithState:objects:count:というメソッドがある。これが、Fast Enumerationの中核となるメソッドのはずだ。

コンパイラが生成するFast Enumerationのためのコード

さて、このメソッドの使われ方だが、これだけを見ていてもどうにもわからない。ここは真っ当に、Appleが公開しているドキュメントをあたることにしよう。ここからは、『Objective-C 2.0プログラミング言語: 高速列挙』のドキュメントをもとに話を進めていく。

まずは、このメソッドがどのように使われているかを調べてみよう。for文を使った次のようなソースコード、

リスト2

for ( existingItem in expression ) {
    stmts
}

は、次のようにコンパイルによって変換されることになる。

リスト3

{
    id                      elem;
    NSFastEnumerationState  __enumState = { 0 };
    id                      __items[16];

    unsigned long           __limit =
            [collection countByEnumeratingWithState:&__enumState objects:__items count:16];
    unsigned                __counter = 0;
    unsigned long           __startMutations = * __enumState.mutationsPtr;

    if (__limit) do {
        __counter = 0;
        do {
            if (__startMutations != * __enumState.mutationsPtr) objc_enumerationMutation(); // 引数不足?
            elem = __enumState.itemsPtr[__counter++];
stmts;
        } while (__counter < __limit);
    } while (__limit = [collection countByEnumeratingWithState:&__enumState objects:__items count:16]);
};

このソースコードは、先ほどのドキュメントのページからコピーしたものから、若干の文法エラーを修正したものだ。ただし、まだ問題が残っている。14行目にあるobjc_enumerationMutation()の呼び出しだ。これはランタイム関数であるが、ヘッダファイル(objc/runtime.h)によると、プロトタイプ宣言は次のようになっている。

リスト4

OBJC_EXPORT void objc_enumerationMutation(id) 
     AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;

id型の引数を1つ取るらしい。だが、ソースコードでは引数は指定されていない。この関数は、ランタイムの中でもプライベート関数となるらしく、挙動についての説明がない。よって憶測にならざるを得ないのだが、前後の関係から見て、__enumState.mutationsPtrを渡すことになるのではないだろうか?

次回は、このソースコードの挙動について調べてみよう。