ARC、块和保留环问题

28

我正在开发一个面向4.0和5.0版本的iOS项目,并使用ARC。

遇到了与Blocks、ARC和从Block外部引用对象有关的问题。这里是一些代码:

 __block AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
   [operation setCompletionBlock:^ {
       if ([operation isCancelled]) {
           return;
       }

... do stuff ...

operation = nil;
}];
在这种情况下,编译器会警告在块中使用'operation'将导致保留循环。在ARC下,__block现在会保留变量。
如果我添加__unsafe_unretained,编译器会立即释放对象,所以显然这不起作用。
我正在针对4.0进行目标设置,因此无法使用__weak。
我尝试过像这样做:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
__block __unsafe_unretained AFHTTPRequestOperation *weakOperation = operation;

但是,当weakOperation不为nil时,在块内部它的任何属性都没有被填充。

考虑到上述项目限制,最好的处理方式是什么?

2个回答

23
假设进展得到保证,保持循环可能正是您想要的。您在块结束时明确打破了保持循环,因此这不是一个永久性的保持循环:当调用块时,该循环被打破。
但是,如果您有其他东西使操作继续存在,则可以将引用存储到 __weak__unsafe_unretained 变量中,然后从块内部使用该变量。除非您出于某种原因需要在块期间更改变量的绑定,否则没有必要对变量进行 __block 限定;由于您不再有需要打破的保持循环,因此不需要给弱变量分配任何内容。

1
我把“无保留循环”这件事牢记在心,我甚至没有像你描述的那样想过它。傻瓜。下一个问题 - 有没有办法消除编译器警告?否则我会疯掉的。 - Hunter
1
请参阅 Clang 用户手册中的“通过 Pragmas 控制诊断”(http://clang.llvm.org/docs/UsersManual.html#diagnostics_pragmas)。您只需要弄清楚要忽略哪个警告标志即可。 - Jeremy W. Sherman
4
顺便说一下,它是#pragma clang diagnostic ignored "-Warc-retain-cycles" - Charlie Groves
7
抱歉,我知道我来晚了,但是要特别注意@JeremyW.Sherman的开场白(“假定进展保证”),尤其是在使用AFNetworking时,因为情况并非如此。在您的示例中,如果操作被取消,则在将操作设置为nil之前返回,类似地,在AFHTTPRequestOperation.m:setCompletionBlockWithSuccess:...中,如果操作被取消,则不会调用完成或错误块,导致您的操作被保留。 - levigroker
即使假设有进展的保证,引用循环通常也不是“你想要的正好”,尽管它可能是可以接受的。 - tc.

1

这似乎是Conrad Stoll在Blocks, Operations, and Retain Cycles中描述的问题,但他的写作漏掉了一些重要的点:

  • __block看起来像是避免在MRC模式下对捕获变量产生强引用的苹果推荐方式,但在ARC模式下完全不必要。在这种情况下,在ARC模式下它完全不必要;虽然轻量级的解决方法在MRC模式下也是不必要的: void * unretainedOperation = operation; ... ^{ AFHTTPRequestOperation * op = unretainedOperation; }
  • 在ARC模式下,您需要一个强引用(以便将其添加到队列中)和一个弱引用/unsafe_unretained引用

最简单的解决方案如下:

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
AFHTTPRequestOperation * __unsafe_unretained unretainedOperation = operation;

[operation setCompletionBlock:^ {
  if ([unretainedOperation isCancelled]) {
    return;
  }
  ... do stuff ...
}];

即使你打破了引用循环,对于Block来说也没有保留AFHTTPRequestOperation的理由(假设操作会一直保持活动状态直到完成处理程序完成,这并不总是可靠的,但通常是正确的,并且如果在调用堆栈中使用self引用,则ARC会假定它是正确的)。

最好的解决方法似乎是更新到最新的AFNetworking,将操作作为参数传递到块中。


__block并非完全没有必要。默认情况下,所有被block保留的变量在其中都将是const,因此您无法更改它们的值。这时__block就派上用场了。 - Yurii Romanchenko

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