【问题标题】:How can I break the retain cycles in my code?如何打破代码中的保留周期?
【发布时间】:2012-07-12 13:38:44
【问题描述】:

最近我被放到了别人的代码库中,到目前为止,我已经能够处理它抛出的大多数事情,但这个有点超出我的想象。有一些保留周期我不知道如何解决。

有一个包装 FROAuthRequest 的自定义对象,FROAuthRequest 有一个完成块,其中还使用了 3 个块,一个解析块、完成块和失败块。完成、完成和失败块都导致了一个保留周期。

我知道原因是对块内 ivars 的引用,但我尝试过的方法没有奏效,请参阅帖子末尾了解我尝试过的内容。

以下代码是我开始尝试修复它之前的样子。代码路径如下:

1:创建请求:

//in the MainViewController.m
SHRequest *request = [api userInfo];

2:创建 SHRequest 的方法

//in API.m
-(SHRequest*)userInfo{

    FROAuthRequest *request = [[FROAuthRequest alloc]initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",SH_URL_API,SH_URL_USER_INFO]] 
                                                    consumer:consumer 
                                                       token:token 
                                                       realm:nil 
                                           signatureProvider:signatureProvider];

    //wrap the FROAuthRequest in our custom object
    //see below for the SHRequest 
    SHRequest *shRequest = [[SHRequest alloc]initWithRequest:request];

    //set the parsing block
    shRequest.parsingBlock = ^id(FROAuthRequest* finishedRequest){

        NSDictionary *jsonResponse = [finishedRequest.responseData objectFromJSONData];

        [user release];
        user = [[SHUser alloc]initWithJSON:[jsonResponse objectForKey:@"user"]];

        //more code

        return [NSDictionary dictionaryWithObjectsAndKeys:user,kUserKey,nil];
   };

   [request release];
   return [shRequest autorelease];
}

3:SHRequest

//in SHRequest.m
-(id)initWithRequest:(FROAuthRequest*)_underlyingRequest{
    if(self = [super init]){
        underlyingRequest = [_underlyingRequest retain];

        //this is the majority of the post processing
        underlyingRequest.completionBlock = ^{
            //if the requests fails call the fail block
            if(underlyingRequest.responseStatusCode != 200){
                if(failBlock != nil)
                    failBlock();
                return;
            }

            if([underlyingRequest.responseData length] > 0){
                [object release];
                object = parsingBlock(underlyingRequest);
                [object retain];

                if((underlyingRequest.error || !object) && failBlock != nil)
                    failBlock();
                else if(finishBlock != nil)
                    finishBlock();
            }else if(failBlock != nil)
                failBlock();
        };

        underlyingRequest.failedBlock = ^{
            if(failBlock)
                failBlock();
        };
    }
return self;
}

4:一旦从 userInfo 方法 (1) 返回 SHRequest,就设置完成和失败块。 (对于这种情况,没有设置 failBlock。

//in MainViewController.m
request.finishBlock = ^{
    NSDictionary *userInfo = request.object;

    //User
    SHUser *user = [userInfo objectForKey:kUserKey];

    //more code

};
[request send];

这是我尝试过的方法
我将 completionBlock 代码移动到启动请求并使用 __block 类型的方法中,泄漏似乎已经消失,但是当完成块运行时,一些 __block vars 是僵尸。

//in SHRequest.m
-(void)send{

    __block void(^fail)(void) = failBlock;
    __block void(^finish)(id) = finishBlock;
    __block id(^parsing)(FROAuthRequest*) = parsingBlock;
    __block FROAuthRequest *req = underlyingRequest;

    underlyingRequest.completionBlock = ^{

        if(req.responseStatusCode != 200){
            if(fail != nil)
                fail();
            return;
        }
        if([req.responseData length] > 0){
           id obj = parsing(req);//<--- parsing is a zombie 

            if((req.error || !obj) && fail != nil)
                fail();
            else if(finish != nil)
                finish(obj);//<--- finish is a zombie
        }else if(fail != nil)
            fail();
    };

    underlyingRequest.failedBlock = ^{
         if(fail)
             fail();
    };

    [underlyingRequest startAsynchronous];
}

关于我做错了什么有什么想法吗?

【问题讨论】:

  • 您能否发布failBlockfinishBlockparsingBlock 的声明方式?另外,这是 ARC 吗?
  • 不,它不是 ARC,并且问题中已经存在完成和解析块,请参阅第 2 点的解析和第 4 点的完成。
  • 所以听起来这些块在被访问之前就被释放了。您是否在属性上使用 copy 所有权修饰符?声明应类似于 @property(copy)id(^parsingBlock)(FROAuthRequest* finishedRequest);
  • 是的,它被复制了,在块执行之前发生了一个自动释放,因为 SHRequest 对象作为一个方法从一个自动释放的对象返回,但我什至不明白这是怎么回事原因...
  • 好的,还有一个问题——你支持 iOS 4,还是只支持 5+?

标签: ios memory-leaks objective-c-blocks


【解决方案1】:

当它们出现并用它们编写几乎所有界面时,我对它们感到兴奋。在我意识到内存管理和可读性的问题之后,这让我回到委托,除非 API 真的 调用块。也许这也会让你的代码看起来更好,如果这不是太多的代码需要改变的话。

【讨论】:

  • 我已经考虑过这一点,但它需要对代码进行大量重构。问题中的请求只是众多请求之一,每个请求都需要解析块中的不同代码。本质上 SHRequest 类是应用程序的核心,对服务器的每个请求都通过它。如果这是唯一的解决方案,我会这样做,但这段代码已经在市场上发布,所以我希望有人可以提供一个不需要耗时重构的解决方案。
  • 块只是带有一些语法糖的对象,内存管理与以前没有什么不同。您可以使用带有块的委托做与以前完全相同的事情。唯一的区别是委托通常由弱引用持有。如果你想对块做同样的事情,你需要持有对self的弱引用,使用__block__weak
  • 块不仅仅是对象,因为它们捕获了它们的环境。 (我认为你不能在“普通”Objective-C 中做到这一点。)这就是为什么我说使用块的内存管理有点不同——创建一个 API 非常容易,让你处理所有的保留周期时间(就像在这种情况下)。某些循环最终会自动中断,而其他循环则必须手动中断,例如弱 self 别名。这通常很笨拙,这就是我说我更喜欢委托的原因,因为委托通常会为您提供更简洁的代码和更轻松的内存管理(= API 获胜)。
【解决方案2】:

首先,如果您正在修改代码库,我建议您借此机会迁移到 ARC。这将使块的生活变得更加轻松。

如果您无论如何都必须筛选所有代码,这可能是值得的……而且 ARC 自动化转换工具非常好。

即便如此,您仍会看到保留周期,但 ARC 加 __weak 是打破保留周期的好方法。

除此之外,您将不得不单独处理每个块构造,而不仅仅是在外部方法中。

【讨论】:

    【解决方案3】:

    如果您获得 EXC_BAD_ACCESS,那么在完成块运行时 SHRequest 对象已经消失,或者(可能不太可能)某些代码过早地清除了您的完成块属性。如果你的目标是 iOS 5,你可以通过将块变量设为弱引用来解决它,这样如果 SHRequest 对象的生命周期在回调之前结束,它们就不会被调用:

    __weak void(^fail)(void) = failBlock;
    

    由于您必须以 iOS4 为目标,我认为最好的选择是将回调移动到方法而不是属性中。在这种情况下,保留周期仍然存在,但它的范围很窄,仅限于 underlyingRequest 的执行。一旦它执行并释放completionBlockfailedBlock,SHRequest 就可以被释放(一旦没有其他东西占用它)。块属性的常见问题是 self 保留了块,它保留了 self,打破循环的唯一方法是要么在块中使用对 self 的弱引用,要么在某个点显式地 nil out 属性,这可以在回调场景中很难做到。以下是您的代码在返回回调块的方法时的大致样子:

    -(void (^)(void))requestFinishedCallback {
        return [^{
            NSDictionary *userInfo = self.object;
    
            //User
            SHUser *user = [userInfo objectForKey:kUserKey];
    
            //more code
        } copy] autorelease];
    }
    
    -(void)send{
        underlyingRequest.completionBlock = ^{
            if(req.responseStatusCode != 200){
                [self requestFailedCallback]();
                return;
            }
            if([req.responseData length] > 0){
                id obj = [self parsingBlock](underlyingRequest);
    
                if (req.error || !obj) {
                    [self requestFailedCallback]();
                }
                else {
                    [self requestFinishedCallback]();
                }
            } else {
                    [self requestFailedCallback]();
            }
        };
    
        underlyingRequest.failedBlock = [self requestFailedCallback];
    
        [underlyingRequest startAsynchronous];
    }
    

    顺便说一句,还有some other good reasons for returning callback blocks from methods

    【讨论】:

      【解决方案4】:

      复制解析/完成/失败块并将请求对象作为块的参数传递似乎已经解决了我的问题

      -(void)send{
      
      __block void(^fail)(void) = [failBlock copy];
      __block void(^finish)(id) = [finishBlock copy];
      __block id(^parsing)(FROAuthRequest*) = [parsingBlock copy];
      __block FROAuthRequest *req = underlyingRequest;
      
      underlyingRequest.completionBlock = ^{
      
          if(req.responseStatusCode != 200){
              if(fail != nil)
                  fail();
              return;
          }
          if([req.responseData length] > 0){
             id obj = parsing(req);
      
              if((req.error || !obj) && fail != nil)
                  fail();
              else if(finish != nil)
                  finish(obj);
          }else if(fail != nil)
              fail();
      };
      
      underlyingRequest.failedBlock = ^{
           if(fail)
               fail();
      };
      
      [underlyingRequest startAsynchronous];
      }
      

      在我需要引用 ivar 或请求本身的任何请求中,我创建一个块 var 并保留它,然后在完成/失败块中释放它

      __block SHRequest *req = [request retain];
      request.finishBlock = ^(id object){
      
          NSDictionary *userInfo = object;
      
          //User
          SHUser *user = [userInfo objectForKey:kUserKey];
      
          //more code
          [req release];
      };
      
      request.failBlock = ^{
          if(req.requestStatusCode == 500)
              //do stuff
          [req release];
      };
      

      通过这种方式 Instruments 不再报告泄漏。

      【讨论】:

        猜你喜欢
        • 2015-09-25
        • 2013-04-06
        • 1970-01-01
        • 1970-01-01
        • 2012-09-29
        • 1970-01-01
        • 2018-05-13
        • 1970-01-01
        • 2012-09-19
        相关资源
        最近更新 更多