如何修复“在此块中强烈捕获'block'可能会导致保留周期”的问题

12

我正在处理这段代码,它会在网络上执行一些漫长的异步操作,并在完成后触发一个完成块,在该块中进行某些测试,如果变量获得特定值,那么另一个漫长的操作应立即开始:

-(void) performOperation
{

    void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request){


        int variable=0;

        // Do completion operation A
        //...
        //...

        // Do completion operation B                
        //Get the variable value

        if(variable>0){
            [self doLengthyAsynchronousOperationWithCompletionBlock: completionBlock];
        }

    };

//Perform the lenhgty operation with the above completionBlock
    [self doLengthyAsynchronousOperationWithCompletionBlock: completionBlock];

}

-(void) doLengthyAsynchronousOperationWithCompletionBlock: completionBlock
{
    //Do some lengthy asynchronous stuff
}

使用这段代码,我从编译器得到了如下警告:

WARNING: Block pointer variable 'completionBlock' is uninitialized when caputerd by the block

我改变了:

void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request)

在:

__block void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request)

但我收到了另一个警告:

WARNING 2: Capturing 'completionBlock' strongly in this block is likely to lead to a retain cycle

我该如何修复这个问题?

谢谢

Nicola


请查看此答案 - Dmitry Zhukov
1个回答

29

警告:当递归块初始化时,块指针变量“completionBlock”未初始化就被块捕获

发生这种情况是因为初始化为递归块的块变量需要__block存储。

  • 块内的变量会被复制,除非使用__block声明,在这种情况下,它们将作为引用传递。
  • 当递归块分配给块变量时,创建发生在赋值之前,并且这样的创建会触发变量复制。鉴于变量尚未被赋值,复制的变量将是错误的值,并且运行块时将导致崩溃。
  • 但是如果我们添加__block,块将使用对变量的引用创建。然后变量将被初始化为已创建的块,块将准备好使用。

警告:在此块中强烈捕获“completionBlock”可能会导致保留循环

原因是块变量是块的强引用,而块本身正在引用该变量(因为如上所述,变量具有__block,因此被引用而不是复制)。

因此,我们需要:

  • 在块内使用弱引用来引用强变量。
  • 在块外使用强引用,以防止块在创建它的方法范围内被释放。
    void(^ completionBlock) (id obj, NSError *err, NSURLRequest *request);
    void(^ __block __weak weakCompletionBlock) (id obj, NSError *err, NSURLRequest *request);
    weakCompletionBlock = completionBlock = ^(id obj,NSError *err, NSURLRequest *request){
        [self lengthyAsyncMethod:weakCompletionBlock];
    };

方法名doLengthyAsynchronousOperationWithCompletionBlock表明该方法可能超出创建块的方法范围。由于编译器不会复制作为参数传递的块,因此该方法负责复制该块。如果我们在块感知代码中使用此块(例如:dispatch_async()),则会自动执行此操作。

如果将此块分配给实例变量,则需要一个@property(copy)和块内弱引用self,但这并非本例情况,因此我们只需使用self。


我尝试了类似这样的事情,似乎可以工作:我将块保存在控制器的副本属性中,然后将该属性作为完成块传递。Nicola - nico9T
你不需要在completionBlock上使用__block,因为它从未在块中使用过!实际上,它从未被任何地方使用过。 - newacct
1
另外,self 不是 OP 警告的重点。我们不知道 self 是否强引用了该块(OP 没有展示足够的代码让我们看到),因此假设它需要是 __weak 是不安全的。 - newacct
仍然,completionBlock 从未被使用过,应该被删除。而 __block on weakCompletionBlock 必要的(因此删除它是不正确的),因为在创建块时尚未分配其值。 - newacct
@newacct 感谢您的评论,我仍需要更多地了解块。然而,我不明白为什么不需要 completionBlock。在我看来,它必须存在才能防止在 lengthyAsyncMethod 有机会复制它之前释放该块。 - Jano
@Jano:没事,你是对的。我忘记了completionBlock稍后要传递给doLengthyAsynchronousOperationWithCompletionBlock: - newacct

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