6

しばらくの間ブロックを使用してきましたが、ARC 環境と非 ARC 環境の両方でメモリ管理について見逃していることがあると感じています。より深く理解することで、多くのメモリ リークを回避できると思います。

AFNetworking は、特定のアプリケーションでのブロックの主な用途です。ほとんどの場合、操作の完了ハンドラー内で、「[self.myArray addObject]」のようなことを行います。

ARC と非 ARC が有効な環境の両方で、Apple のこの記事に従って「自己」が保持されます。

つまり、AFNetworking ネットワーク操作の完了ブロックが呼び出されるたびに、self はそのブロック内に保持され、そのブロックが範囲外になると解放されます。これは、ARC と非 ARC の両方に当てはまると思います。Leaks ツールと Static Analyzer の両方を実行して、メモリ リークを見つけられるようにしました。どれも何も示しませんでした。

しかし、理解できない警告に出くわしたのはつい最近のことでした。この特定の例では ARC を使用しています。

ネットワーク操作の完了と失敗を示す 2 つのインスタンス変数があります

@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;

コードのどこかで、私はこれを行います:

[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
                                                    responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
            _failureBlock(error);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"nothing");
        }];

Xcode は、failureBlock を呼び出す行について、「このブロックで "自己" を強力にキャプチャすると、保持サイクルが発生する可能性が高い」というメッセージとともに不平を言います。私は Xcode が正しいと信じています。そのため、2 つのブロックのいずれも割り当てが解除されません。

ただし、次の質問/観察があります。

1) _failureBlock(error) を "self.failureBlock(error)" (引用符なし) に変更すると、コンパイラは文句を言うのをやめます。何故ですか?これは、コンパイラが見逃しているメモリ リークですか?

2) 一般に、インスタンス変数であるブロックを使用する場合、ARC と非 ARC が有効な環境の両方でブロックを操作するためのベスト プラクティスは何ですか? AFNetworking の完了ブロックと失敗ブロックの場合、これら 2 つのブロックはインスタンス変数ではないため、上記で説明した保持サイクルのカテゴリにはおそらく該当しないようです。しかし、プログレス ブロックを AFNetworking に使用する場合、上記のような保持サイクルを回避するにはどうすればよいでしょうか?

ARC および非 ARC に関するブロックとメモリ管理の問題/解決策について、他の人の意見を聞きたいです。これらの状況はエラーが発生しやすいと感じており、問題を解決するためにこれについて議論する必要があると感じています。

問題があるかどうかはわかりませんが、最新の LLVM で Xcode 4.4 を使用しています。

4

2 に答える 2

6

1)_failureBlock(error)を "self.failureBlock(error)"(引用符なし)に変更すると、コンパイラーは文句を言いなくなります。何故ですか?これはコンパイラが見逃すメモリリークですか?

どちらの場合も保持サイクルが存在します。iOS 5以降をターゲットにしている場合は、自己への弱参照を渡すことができます。

__weak MyClass *weakSelf;
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
    if (weakSelf.failureBlock) weakSelf.failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"nothing");
}];

これで、自己は保持されなくなり、コールバックが呼び出される前に割り当てが解除された場合、コールバックはノーオペレーションになります。ただし、コールバックがバックグラウンドスレッドで呼び出されているときに割り当て解除が行われる可能性があるため、このパターンによってクラッシュが発生する可能性があります。

2)一般に、インスタンス変数であるブロックを使用する場合、ARC環境と非ARC対応環境の両方でブロックを操作するためのベストプラクティスは何ですか?AFNetworkingの完了ブロックと失敗ブロックの場合、これら2つのブロックはインスタンス変数ではないため、上記で説明した保持サイクルのカテゴリにはおそらく分類されないようです。しかし、進行ブロックをAFNetworkingに使用する場合、上記のような保持サイクルを回避するために何ができるでしょうか。

ほとんどの場合、ブロックをインスタンス変数に格納しない方がよいと思います。代わりに、クラスのメソッドからブロックを返す場合でも、保持サイクルはありますが、メソッドが呼び出されてからブロックが解放されるまでしか存在しません。したがって、ブロックの実行中にインスタンスの割り当てが解除されるのを防ぎますが、ブロックが解放されると保持サイクルは終了します。

-(SFCompletionBlock)completionBlock {
    return ^(AFHTTPRequestOperation *operation , id responseObject ) {
        [self doSomethingWithOperation:operation];
    };
}

[self.operation setCompletionBlockWithSuccess:[self completionBlock]
                                      failure:[self failureBlock]
];
于 2012-07-30T17:41:55.557 に答える
2

つまり、AFNetworking ネットワーク操作の完了ブロックが呼び出されるたびに、self はそのブロック内に保持され、そのブロックが範囲外になると解放されます。

いいえ、selfブロックの作成時にブロックによって保持されます。そして、ブロックが解放されると解放されます。

私は Xcode が正しいと信じています。失敗ブロックは自分自身を保持し、自分自身のブロックのコピーを保持するため、2 つのいずれも割り当て解除されません。

保持する問題のブロックselfは、 に渡される完了ブロックsetCompletionBlockWithSuccessです。selfこのブロックへの参照を保持していません。むしろ、self.operation(おそらくある種のNSOperation) は、実行中にブロックを保持します。したがって、一時的にサイクルがあります。ただし、操作の実行が完了すると、サイクルは中断されます。

1) _failureBlock(error) を "self.failureBlock(error)" (引用符なし) に変更すると、コンパイラは文句を言うのをやめます。何故ですか?これは、コンパイラが見逃しているメモリ リークですか?

違いはないはずです。selfどちらの場合もキャプチャされます。コンパイラは、リテイン サイクルのすべてのケースをキャッチできるとは限りません。

于 2012-07-30T08:56:22.037 に答える