7

一連の長方形を対応するアクションに関連付けたいと思っていたので、やろうとしました

struct menuActions {
    CGRect rect;
    SEL action;
};

struct menuActions someMenuRects[] = {
    { { { 0, 0 }, {320, 60 } }, @selector(doSomething) },
    { { { 0, 60}, {320, 50 } }, @selector(doSomethingElse) },
};

しかし、「初期化要素が定数ではありません」というエラーが表示されます。私がやろうとしていることが一般的に許可されていない、またはグローバルスコープで許可されていない、または何らかの小さな句読点の間違いがあるという何らかの理由がありますか?

4

3 に答える 3

23

この答えは理由"initializer element is not constant"です。

次の例を考えます。

SEL theSelector; // Global variable

void func(void) {
  theSelector = @selector(constantSelector:test:);
}

i386アーキテクチャ用に次のようにコンパイルします。

  .objc_meth_var_names
L_OBJC_METH_VAR_NAME_4:
  .ascii "constantSelector:test:\0"

  .objc_message_refs
  .align 2
L_OBJC_SELECTOR_REFERENCES_5:
  .long   L_OBJC_METH_VAR_NAME_4

この部分では、2 つのローカル (アセンブリ コードに関して) '変数' (実際にはラベル)L_OBJC_METH_VAR_NAME_4L_OBJC_SELECTOR_REFERENCES_5. 'variable' ラベルの直前のテキスト.objc_meth_var_namesandは、オブジェクト ファイルのどのセクションに「続くもの」を配置するかをアセンブラに指示します。.objc_message_refsセクションはリンカーにとって意味があります。 L_OBJC_SELECTOR_REFERENCES_5のアドレスに初期設定されていますL_OBJC_METH_VAR_NAME_4

実行ロード時、プログラムが実行を開始する前に、リンカは次のような処理を行います。

  • セクション内の各エントリを反復処理します.objc_message_refs
  • 各エントリは、最初は0終了C文字列へのポインタに設定されます。
  • この例では、ポインターは最初に、文字列 L_OBJC_METH_VAR_NAME_4を含む のアドレスに設定されます。ASCII C"constantSelector:test:"
  • 次に実行 sel_registerName("constantSelector:test:") し、戻り値を に格納し L_OBJC_SELECTOR_REFERENCES_5ます。プライベートな実装の詳細を知っているリンカは、sel_registerName()文字どおりに呼び出すことはできません。

基本的に、リンカーはこの例のロード時にこれを実行します。

L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");

"initializer element is not constant"これが、初期化要素がコンパイル時に定数でなければならない理由です。プログラムが実行を開始するまで、値は実際にはわかりません。その場合でも、struct宣言は別のリンカ セクションである.dataセクションに格納されます。SELリンカーは、セクション内の値を更新する方法しか認識しておらず、その実行時に計算された値を から任意の場所に.objc_message_refs「コピー」する方法はありません。SEL.objc_message_refs.data

Cソースコードは...

theSelector = @selector(constantSelector:test:);

... になります:

  movl    L_OBJC_SELECTOR_REFERENCES_5, %edx // The SEL value the linker placed there.
  movl    L_theSelector$non_lazy_ptr, %eax   // The address of theSelector.
  movl    %edx, (%eax)                       // theSelector = L_OBJC_SELECTOR_REFERENCES_5;

リンカはプログラムが実行される前にすべての作業L_OBJC_SELECTOR_REFERENCES_5を行うため、 を呼び出した場合に得られる値とまったく同じ値が含まれていますsel_registerName("constantSelector:test:")

theSelector = sel_registerName("constantSelector:test:");

違いは、これは関数呼び出しであり、セレクターが既に登録されている場合は関数がセレクターを見つける実際の作業を行うか、SELセレクターを登録するために新しい値を割り当てるプロセスを実行する必要があることです。これは、定数値をロードするだけの場合よりもかなり遅くなります。これは「遅く」なりますが、任意のC文字列を渡すことができます。これは、次の場合に役立ちます。

  • セレクターはコンパイル時には認識されません。
  • 直前にsel_registerName()呼び出されるまで、セレクターはわかりません。
  • 実行時にセレクターを動的に変更する必要があります。

すべてのセレクターは、それぞれを正確に 1 回sel_registerName()登録する を通過する必要があります。SELこれには、特定のセレクターに対して、どこでも 1 つの値しかないという利点があります。実装のプライベートな詳細ですSELが、「通常」char *は、セレクターC文字列テキストのコピーへのポインターにすぎません。

今、あなたは知っています。そして、知ることは戦いの半分です!

于 2009-09-12T03:27:50.903 に答える
-1

ここで NSCell を再発明しているようです。メニューを実装したい場合は、既存の UI クラスを使用してみませんか?

于 2009-09-12T14:57:52.050 に答える