ARC対応コードで、ブロックベースのAPIを使用しているときに、潜在的な保持サイクルに関する警告を修正する方法は?
警告:Capturing 'request' strongly in this block is likely to lead to a retain cycle
次のコードスニペットによって生成されます:
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
[request setCompletionBlock:^{
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
// ...
}];
警告は、ブロック内のオブジェクトrequest
の使用にリンクされています。
自分への返信:
ドキュメントの私の理解では、キーワードblock
を使用し、ブロック内で使用した後に変数をnilに設定することは問題ないはずですが、それでも警告が表示されます。
__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
[request setCompletionBlock:^{
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
request = nil;
// ....
}];
更新:キーワード '_weak'の代わりに ' _ block'を使用し、一時変数を使用するようにしました:
ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;
[request setCompletionBlock:^{
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
// ...
}];
IOS 4もターゲットにしたい場合は、__unsafe_unretained
の代わりに __weak
。同じ動作ですが、オブジェクトが破棄されると、ポインターは自動的にnilに設定されるのではなく、ぶら下がります。
この問題は、リクエストへの強い参照を持つリクエストにブロックを割り当てるために発生します。ブロックはリクエストを自動的に保持するため、元のリクエストはサイクルのために割り当て解除されません。理にかなっていますか?
リクエストオブジェクトに__blockのタグを付けて、自分自身を参照できるようにしているため、奇妙です。これを修正するには、弱参照とともに itを作成します。
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;
[request setCompletionBlock:^{
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
// ...
}];
ブロックに自己を保持するために発生します。ブロックはselfからアクセスされ、selfはブロックで参照されます。これにより、保持サイクルが作成されます。
self
の弱い参照を作成して、これを解決してください
__weak typeof(self) weakSelf = self;
operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];
Xcodeコンパイラには保持サイクルの識別子に問題がある場合があるため、completionBlockを保持していないことが確実な場合は、次のようにコンパイラフラグを設定できます。
#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
-(void)someMethod {
}
Guillaumeが提供するソリューションを試してみると、デバッグモードではすべて問題ありませんが、リリースモードではクラッシュします。
ターゲットがiOS 4.3であるため、__ weakではなく__unsafe_unretainedを使用しないことに注意してください。
SetCompletionBlock:がオブジェクト "request"で呼び出されるとコードがクラッシュします:リクエストの割り当てが解除されました...
したがって、このソリューションは、デバッグモードとリリースモードの両方で機能します。
// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code
self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;
[self.request setCompletionBlock:^
{
[dataModel processResponseWithData:dataModel.request.receivedData];
}];
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
blockRequest = nil;
// ....
}];