1

奇妙な状況があります。関数にいくつかのローカル変数があります。

JSContext *cx = ...;
jsval successCb = ...;

これらのパラメーターを取る関数呼び出しがあります。

//JS_RemoveValueRoot(JSContext *cx, jsval *vp);
JS_RemoveValueRoot(cx, &successCb); //works

上記は正常にコンパイルされます。ただし、代わりに次のものを使用すると、コンパイル時エラーが発生します。

id foo = ^() {
    JS_RemoveValueRoot(cx, &successCb);
}

文字通り、行をコピーして貼り付けると、ブロックの外側にある場合はコンパイルされますが、そうでない場合はコンパイルされません。エラーは次のとおりです。

No matching function for call to 'JS_RemoveValueRoot'

ブロッククロージャの実装方法に関して、舞台裏で何かが起こっているのではないかと思いますが、これを理解できるほどObjective Cに精通していません。これによりコンパイル時エラーが発生するのはなぜですか? また、どのように修正すればよいですか?

編集:次のようにすると、コンパイル時エラーが発生しなくなりますが、これは私には意味がありません。これは常に悪いことなので、説明が必要です...

id foo = ^() {
    jsval localSuccessCb = successCb;
    JS_RemoveValueRoot(cx, &localSuccessCb);
};
4

2 に答える 2

1

それはもっと複雑です。はい、差し迫った問題は、__blockキャプチャされていないすべての変数がconstブロック内にあるということです。したがって、ブロック内にcxは typeがJSContext * constあり、successCbtype がありconst jsvalます。に渡すconst jsval *ことはできませんjsval *。しかし、変数が である理由を最初に理解する必要がありますconst

ブロックは、作成時に非__block変数を値でキャプチャします。つまり、ブロック内の変数のコピーとブロック外のコピーは、同じ名前を持っていても、異なる独立した変数です。そうでない場合const、ブロック内の変数を変更し、外部で変更されることを期待したくなるかもしれませんが、そうではありません。(もちろん、反対の問題は依然として発生します。ブロックの外側で変数を変更することはできます。変数はconstではないため、なぜブロックの内側で変更されないのか疑問に思います。)__blockは、この問題を解決するために、ブロックの内外で共有される変数。

次に、変数が十分でない理由を考えることが重要です。const変数の値だけが必要な場合は、constコピーでも構いません。constうまくいかない場合、通常は変数に代入する必要があるためです。変数への非ポインターJS_RemoveValueRootが必要なのは何ですか? const変数に代入するのですか?(そうであれば、ブロック外の新しい値を気にしますか? そうでない場合は、変数をブロック内のconstconst変数に割り当てることができるためです。)

それはもっと複雑であることがわかりました。のドキュメントによると、JS_Remove*Root指している変数の値を使用することも、変数を設定する必要もありません。むしろ、変数のアドレスが必要であり、これは に渡されるアドレスと一致する必要がありますJS_Add*Root。(実際、彼らがしていることにポインタが必要かどうかさえわかりません。)ブロックを囲む関数の本体で、ブロックの外側でconstそれが行われたと仮定しています。JS_AddValueRoot(あなたが言っsuccessCbたので、これはローカル変数であると思います。したがって、この関数内にある必要があります。ブロック内にある場合、ブロックsuccessCbのローカル変数になる可能性があるため、意味がありません。捕まる。)

変数自体のアドレスは重要なので、さまざまなブロック変数キャプチャ モードで何が起こるかを考えてみましょう。__block内側と外側に 2 つの別々のコピー (したがって 2 つの別々のアドレス) があるため、非変数は明らかに適切ではありません。したがって、 と に与えられたアドレスは一致AddRemoveません。__block変数は共有され、はるかに優れています。

__blockただし、変数が一致しない可能性があるという問題がまだあります。__block変数のアドレスは時間の経過とともに変化する可能性があります。これは、ブロックの実装方法の詳細に入ります。現在の実装では、__block変数はスタック上で始まる特別な構造 (一種の「オブジェクト」) に保持されますが、変数をキャプチャするブロックがコピーされると、動的にヒープに「移動」されます。割り当てられた構造。これは、変数をキャプチャするブロック オブジェクトがスタック上で開始される方法と非常に似ていますが、コピー時にヒープに移動されます。最初にスタックに置くのは最適化であり、保証されていません。しかし、現在はそうです。の__block変数自体は、実際にはこの構造内の変数へのアクセスであり、この構造がどこにあるかを追跡するポインターを介してアクセスされます。構造体がスタックからヒープに移動すると、式の値が&successCb変化することがわかります。(これは通常の C では不可能です。) したがって、一致するアドレスを取得するには、変数のアドレスを に渡すときに移動が既に発生していることを確認する必要がありますAdd。それをキャプチャするブロックを強制的にコピーすることで、これを行うことができる場合があります。

于 2013-10-29T09:45:12.573 に答える