web-dev-qa-db-ja.com

ココアは強力なポインター対コピーとしてブロックします

私は強い参照を持っていたポインターのようにブロックで数回作業しました

コピーを使用する必要があると聞きましたが、生のオブジェクトではなく、ブロックをポインターとして操作することの意味は何ですか?

コンパイラから文句を言われたことは一度もありません。

@property (nonatomic, strong) MyBlock block;

しかし、使用する必要があります

@property (nonatomic, copy) MyBlock block;

私の知る限り、ブロックは単なるオブジェクトですが、とにかくコピーを優先するのはなぜですか?

21
Peter Lapisu

短い答え

答えは歴史的です。現在のARCコードでは、copyを使用する必要はなく、strongプロパティで問題ありません。たとえば、ローカル変数とグローバル変数についても同じことが言えます。

長い答え

他のオブジェクトとは異なり、ブロックがスタックに格納される場合があります、これは実装の最適化であり、他のコンパイラの最適化と同様に必要です。 、記述されたコードに直接影響を与えません。この最適化は、ブロックが作成され、メソッド/関数の引数として渡され、その関数で使用されてから破棄される一般的なケースに役立ちます。ブロックはスタックにすばやく割り当てられ、ヒープなしで破棄できます(動的メモリプール)。関与している。

これをローカル変数と比較してください。(a)スタックに作成され、(b)所有する関数が自動的に破棄されます/メソッドが返され、(c)所有している関数によって呼び出されたメソッド/関数にアドレスで渡すことができます。ローカル変数のアドレスを保存して使用することはできませんそれ自体の関数/メソッドが戻りました-変数はもう存在しません。

ただし、オブジェクトは、作成する関数/メソッド(必要な場合)よりも長持ちすることが期待されるため、ローカル変数とは異なり、ヒープに割り当てられます。そして、それらの作成関数/メソッドの戻りに基づいて自動的に破棄されるのではなく、それらがまだ必要かどうかに基づいて破棄されます。ここでの「必要」は、最近ARCによって自動的に決定されます。

スタック上にブロックを作成すると、一般的なケースが最適化される場合がありますが、問題も発生します。オブジェクトがよく行うように、ブロックが作成者よりも長持ちする必要がある場合は、作成者スタックが破棄される前に、ブロックをヒープに移動する必要があります。

ブロック実装が最初にリリースされたとき、スタックにブロックを格納する最適化がプログラマーに表示されました。当時のコンパイラーは、必要に応じてブロックをヒープに移動することを自動的に処理できなかったためです。プログラマーは関数block_copy()自分でやる。

このアプローチは低レベルのCの世界では場違いではないかもしれませんが(ブロックはC構造です)、高レベルのObjective-Cプログラマーがコンパイラーの最適化を手動で管理することは実際には良くありません。 Appleは、コンパイラの改善の新しいバージョンをリリースしました。その初期の段階で、プログラマはblock_copy(block)[block copy]に置き換えて、通常のObjective-に適合させることができると言われました。その後、コンパイラは必要に応じてブロックをスタックから自動的にコピーし始めましたが、これは必ずしも公式に文書化されていませんでした。

しばらくの間、スタックからブロックを手動でコピーする必要はありませんでしたが、Appleはその起源を無視することはできず、そうすることを「ベストプラクティス」と呼んでいます。これは確かに議論の余地があります。 Appleの最新バージョンである2014年9月 ブロックの操作 、彼らはブロック値のプロパティはcopyを使用する必要があると述べましたが、すぐにクリーンになります(emphasis追加):

注:キャプチャされた状態を元のスコープ外で追跡するにはブロックをコピーする必要があるため、プロパティ属性としてcopyを指定する必要があります。 自動参照カウントを使用する場合、これは自動的に行われるため、心配する必要はありませんが、プロパティ属性で結果の動作を表示することをお勧めします。

「結果の動作を表示する」必要はありません-最初にブロックをスタックに格納することは最適化であり、他のコンパイラ最適化と同じようにコードに対して透過的である必要がありますコードは、プログラマーの関与なしにパフォーマンス上の利点を得るはずです。

ARCと現在のClangコンパイラを使用している限り、ブロックを他のオブジェクトと同じように扱うことができます。ブロックは不変であるため、コピーする必要はありません。 Appleを信頼してください。「手作業で行った古き良き時代」に懐かしく見えても、コードに歴史的なリマインダーを残すことをお勧めします。copyは必要ありません。

あなたの直感は正しかった。

48
CRD

プロパティの所有権修飾子について質問しています。これは、プロパティが合成(または自動合成)されている場合、プロパティの合成(または自動合成)ゲッターおよび/またはセッターに影響します。

この質問に対する答えは、MRCとARCで異なります。

  • MRCでは、プロパティ所有権修飾子には、assignretain、およびcopyが含まれます。 strongはARCで導入され、strongがMRCで使用される場合、それはretainと同義です。したがって、質問はretaincopyの違いについてであり、copyのセッターは指定された値のコピーを保存するため、多くの違いがあります。

    ブロックを作成したスコープ外で使用するには、ブロックをコピーする必要があります(ブロックリテラルを使用)。プロパティは、関数呼び出し間で存続するインスタンス変数として値を格納し、誰かがそれが作成されたスコープから占有されていないブロックを割り当てる可能性があるため、慣例では、それをコピーする必要があります。 copyは正しい所有権修飾子です。

  • ARCでは、strongは基礎となるインスタンス変数を__strongにし、copyもそれを__strongにし、コピーセマンティクスをセッターに追加します。ただし、ARCは、値がブロックポインタタイプの__strong変数に保存されるたびに、コピーが実行されることも保証します。プロパティの型はMyBlockで、これはブロックポインタ型のtypedefだと思います。したがって、所有権修飾子がstrongの場合、コピーは引き続きセッターで実行されます。したがって、ARCでは、このプロパティにstrongcopyを使用しても違いはありません。

ただし、この宣言がMRCとARCの両方で使用される可能性がある場合(ライブラリのヘッダーなど)、両方の場合に正しく機能するようにcopyを使用することをお勧めします。

3
newacct
what is the implication in working with blocks as pointers and not with the raw object?

生の値を使用することはありません。常にブロックへのポインタがあります。ブロックはオブジェクトです。

あなたの特定の例を見て、私はあなたがブロックを維持したいと思っていると思います、 "だからとにかくコピーを好む理由"enter code here?まあ、それは安全性の問題です(この例はMike Ashブログから取られています)。ブロックはスタックに割り当てられるため(Objective-cの残りのオブジェクトのようにヒープには割り当てられない)、次のようにすると次のようになります。

[dictionary setObject: ^{ printf("hey hey\n"); } forKey: key];

現在のスコープのスタックフレームにブロックを割り当てているため、スコープが終了すると(たとえば、辞書を返す)、スタックフレームが破棄され、ブロックがそれに伴います。だからあなたは自分自身にぶら下がっているポインタを手に入れました。 マイクの記事 を完全に読むことをお勧めします。とにかく、ブロックを割り当てるときにそれをコピーする場合は、strongプロパティを使用できます。

self.block = [^{} copy];

編集:マイクの記事の日付を見た後、これはARC以前の動作であると思います。 ARCでは、期待どおりに機能しているようで、クラッシュすることはありません。

Edit2:非ARCで実験した後、それもクラッシュしません。ただし、この例では、ARCの使用の有無によって異なる結果が示されています。

void (^block[10])();

int i = -1;
while(++i < 10)
    block[i] = ^{ printf("%d\n", i); };


for(i = 0; i < 10; i++)
    block[i]();

さまざまな結果についてMikeAsheを引用します。

最初のケースで10個の9を出力する理由は非常に単純です。ループ内で作成されたブロックの有効期間は、ループの内部スコープに関連付けられています。ブロックは、ループの次の反復時、およびループを離れるときに破棄されます。もちろん、「破棄」とは、スタック上のそのスロットを上書きできることを意味します。コンパイラがループを通過するたびに同じスロットを再利用することがたまたま発生するため、最終的には配列が同じポインタで埋められ、同じ動作が得られます。

2
Rui Peres

注:元のスコープ外でキャプチャされた状態を追跡するにはブロックをコピーする必要があるため、プロパティ属性としてcopyを指定する必要があります。自動参照カウントを使用する場合、これは自動的に行われるため、心配する必要はありませんが、プロパティ属性で結果の動作を表示することをお勧めします。詳細については、 Blocks Programming Topics を参照してください。

0
Tai Le