在启用ARC的代码中修复警告“在此块中强烈捕获[一个对象]可能导致保留循环”

144

在启用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对象相关联。


1
你应该使用responseData而不是rawResponseData,请查看ASIHTTPRequest文档。 - 0xced
7个回答

168

回复自己:

根据文档,使用关键字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。


8
根据 ARC 文档,看起来你需要使用 "__unsafe_unretained __block" 一起来获取与使用 ARC 和 blocks 相同的行为。 - Hunter
4
当我将前两行合并时,会出现此警告:“将保留对象分配给弱变量;对象将在分配后释放”。 - Guillaume
1
@Guillaume 感谢您的回复,不知何故我忽略了临时变量,尝试后警告消失了。您知道这是为什么吗?它只是欺骗编译器以抑制警告,还是警告实际上已不再有效? - Chris Wagner
2
我已经发布了一个后续问题:https://dev59.com/r2ox5IYBdhLWcg3w9o-i - barfoon
3
有人能解释一下为什么需要使用__block和__weak关键字吗?我猜是因为创建了一个保留循环,但我没有看到它。创建一个临时变量如何解决这个问题? - user798719
显示剩余6条评论

50

问题发生的原因是您将一个对请求具有强引用的块分配给了请求。该块将自动保留请求,因此由于循环而不会使原始请求解除分配内存。明白吗?

这很奇怪,因为您正在使用 __block 对请求对象进行标记,以便它可以引用自身。您可以通过创建一个弱引用 它一起来修复这个问题。

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];

__weak ASIHTTPRequest *wrequest = request; 对我不起作用。它会报错。我使用了 __block ASIHTTPRequest *blockRequest = request;。 - Ram G.

14

由于在块中保留了self,因此会导致此问题。块将从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];

这是正确的答案,应该被记录下来。 - Benjamin

6

有时候xcode编译器会在识别保留周期时出现问题,所以如果你确定没有保留completionBlock,可以像这样设置编译器标志:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}

1
有人可能会认为这是不良设计,但我有时会创建独立的对象,它们在内存中挂起,直到完成异步任务。它们由一个completionBlock属性保留,该属性包含一个对self的强引用,从而创建一个有意的保留周期。completionBlock包含self.completionBlock=nil,释放completionBlock并打破保留周期,允许对象在任务完成后释放内存。你的回答很有用,可以帮助减少我执行此操作时出现的警告。 - hyperspasm
1
说实话,一个人正确而编译器出错的可能性非常小。因此,我会说仅仅忽略警告是有风险的做法。 - Max MacLeod

3
当我尝试Guillaume提供的解决方案时,在调试模式下一切正常,但在发布模式下崩溃。
请注意,不要使用__weak,而应使用__unsafe_unretained,因为我的目标是iOS 4.3。
当在对象“request”上调用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];        
}];

有趣的解决方案。你弄清楚为什么它在发布模式下崩溃而在调试模式下没有了吗? - Valerio Santinelli

2
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

}];

__weak 和 __block 引用的区别是什么?


-6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接