6

同じ形式の他の質問をいくつか見ましたが、a)提供された回答を理解できないか、b)それらの状況が私のものとどのように似ているかわかりません。

UIViewのすべてのサブビューを再帰的に評価し、テストに合格したサブビューの配列を返すように、UIViewにカテゴリを記述しています。コンパイラの警告が発生する場所に注意しました。

-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *view, BOOL *stop))test {

   __block BOOL *stop = NO;

    NSArray*(^__block evaluateAndRecurse)(UIView*);
    evaluateAndRecurse = ^NSArray*(UIView *view) {
        NSMutableArray *myPassedChildren = [[NSMutableArray alloc] init];
        for (UIView *subview in [view subviews]) {
            BOOL passes = test(subview, stop);
            if (passes) [myPassedChildren addObject:subview];
            if (stop) return myPassedChildren;


            [myPassedChildren addObjectsFromArray:evaluateAndRecurse(subview)];
            // ^^^^ Compiler warning here ^^^^^
            // "Capturing 'evaluateAndRecurse' strongly in this block 
            // is likely to lead to a retrain cycle"
        }
        return myPassedChildren;
    };

    return evaluateAndRecurse(self);
}

__blockまた、ブロックの宣言に修飾子を含めないと、bad_accessエラーが発生します(^__block evaluateAndRecurse)。誰かがその理由を説明できれば、それも非常に役立ちます。ありがとう!

4

2 に答える 2

8

ここでの問題は、ブロックevaluteAndRecurse()がそれ自体をキャプチャすることです。つまり、コピーされることがある場合(あなたの場合はそうなるとは思いませんが、少し些細な場合はそうなる可能性があります)、ブロックはそれ自体を保持します。保持サイクルを壊すものは何もないので、永遠に生きます。

編集:Ramy Al Zuhouriは良い点を述べました__unsafe_unretained、ブロックへの唯一の参照で使用することは危険です。ブロックがスタックに残っている限り、これは機能しますが、ブロックをコピーする必要がある場合(たとえば、親スコープにエスケープする必要がある場合)、ブロックの__unsafe_unretained割り当てが解除されます。次の段落は、推奨されるアプローチで更新されています。

ここでおそらくやりたいこと__unsafe_unretainedは、ブロックも含むでマークされた別の変数を使用し、その別の変数をキャプチャすることです。これにより、それ自体が保持されなくなります。を使用することもできます__weakが、ブロックが呼び出されている場合はブロックが存続している必要があることがわかっているため、弱参照の(ごくわずかな)オーバーヘッドを気にする必要はありません。これにより、コードは次のようになります。

NSArray*(^__block __unsafe_unretained capturedEvaluteAndRecurse)(UIView*);
NSArray*(^evaluateAndRecurse)(UIView*) = ^NSArray*(UIView *view) {
    ...
        [myPassedChildren addObjectsFromArray:capturedEvaluateAndRecurse(subview)];
};
capturedEvaluateAndRecurse = evaluteAndRecurse;

または、ブロックへのポインターをキャプチャすることもできます。これは同じ効果がありますが、ブロックのインスタンス化の後ではなく、前にポインターを取得できます。これは個人的な好みです。また、__block:を省略できます。

NSArray*(^evaluateAndRecurse)(UIView*);
NSArray*(^*evaluteAndRecursePtr)(UIView*) = &evaluateAndRecurse;
evaluateAndRecurse = ^NSArray*(UIView*) {
    ...
        [myPassedChildren addObjectsFromArray:(*evaluateAndRecursePtr)(subview)];
};

の必要性に関しては__block、それは別の問題です。がない場合__block、ブロックインスタンスは実際に変数の前の値をキャプチャします。ブロックが作成されると、マークされていないキャプチャされた変数は、ブロックがインスタンス化された時点での状態__blockのコピーとして実際に保存されることに注意してください。constまた、ブロックは変数に割り当てられるcapturedEvaluteAndRecurseに作成されるため、割り当て前の変数の状態をキャプチャすることを意味します。これは、 nil(ARCの下で、そうでない場合はガベージメモリになります)。

本質的に、特定のブロックインスタンスは、実際には、キャプチャされた変数ごとにivarを持つ非表示クラスのインスタンスであると考えることができます。したがって、コードを使用すると、コンパイラは基本的に次のように処理します。

// Note: this isn't an accurate portrayal of what actually happens
PrivateBlockSubclass *block = ^NSArray*(UIView *view){ ... };
block->stop = stop;
block->evaluteAndRecurse = evaluateAndRecurse;
evaluteAndRecurse = block;

うまくいけば、これにより、現在の値ではなく以前の値をキャプチャする理由が明確になりevaluateAndRecurseます。

于 2013-01-08T20:00:50.773 に答える
0

私は似たようなことをしましたが、新しいアレイを割り当てる時間を短縮するために別の方法で、問題はありませんでした。次のようにメソッドを調整してみてください。

- (void)addSubviewsOfKindOfClass:(id)classObject toArray:(NSMutableArray *)array {

    if ([self isKindOfClass:classObject]) {

        [array addObject:self];
    }

    NSArray *subviews = [self subviews];
    for (NSView *view in subviews) {

        [view addSubviewsOfKindOfClass:classObject toArray:array];
    }
}
于 2013-01-08T20:03:58.267 に答える