在启用 ARC 的代码中修复警告“在此块中强烈捕获 [an object] 可能会导致保留周期”

Posted

技术标签:

【中文标题】在启用 ARC 的代码中修复警告“在此块中强烈捕获 [an object] 可能会导致保留周期”【英文标题】:Fix warning "Capturing [an object] strongly in this block is likely to lead to a retain cycle" in ARC-enabled code 【发布时间】:2011-11-04 12:42:41 【问题描述】:

在启用 ARC 的代码中,如何在使用基于块的 API 时修复有关潜在保留周期的警告?

警告:Capturing 'request' strongly in this block is likely to lead to a retain cycle

由此产生的sn-p代码:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

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

警告与块内对象request的使用有关。

【问题讨论】:

您可能应该使用responseData 而不是rawResponseData,请查看ASIHTTPRequest 文档。 【参考方案1】:

查看 Apple 开发者网站上的文档:https://developer.apple.com/library/prerelease/ios/#documentation/General/Conceptual/ARCProgrammingGuide/Introduction.html#//apple_ref/doc/uid/TP40011029

页面底部有一个关于保留周期的部分。

【讨论】:

【参考方案2】:

回复我自己:

我对文档的理解是使用关键字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。

【讨论】:

基于 ARC 文档,听起来您需要一起使用 __unsafe_unretained __block 才能在使用 ARC 和块时获得与以前相同的行为。 @SeanClarkHess :当我合并前两行时,我收到以下警告:“将保留对象分配给弱变量;分配后对象将被释放” @Guillaume 感谢您的回复,一些我忽略了临时变量的方式,尝试过并且警告消失了。你知道为什么会这样吗?它只是欺骗编译器来抑制警告还是警告实际上不再有效? 我已经发布了一个后续问题:***.com/questions/8859649/… 有人能解释一下为什么需要 __block 和 __weak 关键字吗?我想有一个保留周期正在创建,但我没有看到它。以及创建临时变量如何解决问题?【参考方案3】:

当我尝试 Guillaume 提供的解决方案时,在 Debug 模式下一切正常,但在 Release 模式下崩溃。

注意不要使用 __weak 而是 __unsafe_unretained,因为我的目标是 iOS 4.3。

当 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];        
];

【讨论】:

有趣的解决方案。你知道为什么它在发布模式而不是在调试模式下崩溃了吗?【参考方案4】:

出现此问题的原因是您将块分配给其中对请求具有强引用的请求。该块将自动保留请求,因此原始请求不会因为循环而释放。有意义吗?

这很奇怪,因为您使用 __block 标记请求对象,以便它可以引用自身。您可以通过创建一个弱引用alongside来解决这个问题。

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;【参考方案5】:
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

];

what the difference between __weak and __block reference?

【讨论】:

【参考方案6】:

这是由于将 self 保留在块中。 Block 会从 self 中访问,而 self 在 block 中被引用。这将创建一个保留周期。

尝试通过创建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];

【讨论】:

这是正确答案,应注意【参考方案7】:

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

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

-(void)someMethod 

【讨论】:

有些人可能会说这是糟糕的设计,但我有时会创建独立的对象,这些对象会在内存中挂起,直到它们完成异步任务。它们由包含对 self 的强引用的 completionBlock 属性保留,从而创建了一个有意的保留循环。 completionBlock包含self.completionBlock=nil,释放completionBlock,打破retain循环,允许任务完成后从内存中释放对象。您的回答有助于消除我执行此操作时出现的警告。 说实话,一个正确而编译器错误的可能性非常小。所以我会说只是超过警告是有风险的事情

以上是关于在启用 ARC 的代码中修复警告“在此块中强烈捕获 [an object] 可能会导致保留周期”的主要内容,如果未能解决你的问题,请参考以下文章

更新到 iOS 6.1 后出现 ARC Retain Cycle

在 ARC 中启动新的 Cocos2d-iPhone 项目后出现内存警告。初学者

Roslyn:如何修复 RS2008 警告?

在 ARC 中收到内存警告

如何修复警告:扩展初始化列表?

启用 ARC 后,NSComparator 在排序 NSArray 时改变行为