了解ARC下块的细微保留周期

时间:2014-04-19 14:46:06

标签: objective-c automatic-ref-counting objective-c-blocks retain-cycle

我正在研究我从 Matt Galloway Effective Objective-C 一书中获取的代码片段。该片段如下(我已经修改了一点)。

- (void)downloadData {
    NSURL *url = // alloc-init
    NetworkFetcher *networkFetcher =
        [[NetworkFetcher alloc] initWithURL:url];
    [networkFetcher startWithCompletionHandler:^(NSData *data){
        NSLog(@"Request URL %@ finished", networkFetcher.url);
        _fetchedData = data;
    }];
    // ARC will put a release call for the networkFetcher here
}

如作者所述,这种模式由不同的网络库使用,并且存在保留周期。保留周期对我来说非常明显,因为如果你从对象图的角度考虑,networkFetcher实例通过completionHandler属性(copy ied)保留块,而块保留networkFetcher,因为它在NSLog中使用它。

现在,为了打破阻止,NetworkFetcher必须在完成下载已请求的数据后将完成处理程序设置为nil

// in NetworkFetcher.m class
- (void)requestCompleted {

    if(self.completionHandler) {
        // invoke the block
        self.completionHandler();
    }

    self.completionHandler = nil;
}

确定。这样就不再有保留周期了。该块在运行时会释放对networkFetcher的引用,networkFetcher使nil引用该块。

现在,我的问题是关于代码段的执行流程。以下一系列操作是否正确?

  1. networkFetcher运行完成处理程序
  2. 执行该块
  3. 该块释放对networkFetcher
  4. 的引用
  5. networkFetcher释放对块的引用
  6. 我的怀疑依赖于行动3)和4)。如果3)在4)之前执行,则没有人引用networkFetcher,因此可以在任何执行时释放它(ARC将在downloadData结束时发布释放调用)。我错了还是错过了什么?

    希望问题清楚。

2 个答案:

答案 0 :(得分:3)

// in NetworkFetcher.m class
- (void)requestCompleted {

    if(self.completionHandler) {
        // invoke the block
        self.completionHandler();
    }

    self.completionHandler = nil;
}

在设置为nil之前执行块。在此方法中块的执行是同步的 - 在完成执行之前不会发生任何事情。请记住,块的存在 not 意味着内部的代码将异步执行。

一旦执行该块,它就不会释放它的引用,因为该块仍然作为网络提取器实例的属性存在。如果你有点奇怪,你可以再次执行它。

该块仅释放它在解除分配时捕获的对象 - 当completionHandler属性设置为nil(即块执行后)时会发生这种情况。

答案 1 :(得分:1)

步骤更像是这样:

  1. 网络提取程序完成请求,然后调用阻止。
  2. 块执行
  3. 没有其他事情发生。由于存在保留周期,因此网络提取器无法解除分配。

    IFF网络提取器显式在调用它之后将其完成块ivar设置为nil,你得到这个:

    最初,完成块的引用计数为+1。

    1. 网络提取程序完成请求,然后调用阻止。
    2. 该块以异步方式执行。 (块的释放计数等于+1)。
    3. 网络提取程序将块设置为nil(释放-1)
    4. 块结束(释放-1)并且块释放,并且捕获的可保留变量被释放(包括networkFetcher指针)。
    5. 还有一些其他方法可以阻止保留周期:

      - (void)downloadData {
          NSURL *url = // alloc-init
          NetworkFetcher *networkFetcher =
              [[NetworkFetcher alloc] initWithURL:url];
          [networkFetcher startWithCompletionHandler:^(NSData *data){
              NSLog(@"Request URL %@ finished", url);
              _fetchedData = data;
          }];
      }
      
      
      - (void)downloadData {
          NSURL *url = // alloc-init
          NetworkFetcher *networkFetcher =
              [[NetworkFetcher alloc] initWithURL:url];
          __block NetworkFetcher* blockNetworkFeatcher = networkFetcher;
          [networkFetcher startWithCompletionHandler:^(NSData *data){
              NSLog(@"Request URL %@ finished", blockNetworkFeatcher.url);
              _fetchedData = data;
              blockNetworkFeatcher = nil;
          }];
      }
      
      - (void)downloadData {
          NSURL *url = // alloc-init
          NetworkFetcher *networkFetcher =
              [[NetworkFetcher alloc] initWithURL:url];
          __weak NetworkFetcher* weakNetworkFeatcher = networkFetcher;
          [networkFetcher startWithCompletionHandler:^(NSData *data){
              NSLog(@"Request URL %@ finished", weakNetworkFeatcher.url);
              _fetchedData = data;
          }];
      }