如何在代码中打破保留周期?

时间:2012-07-12 13:38:44

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

最近我被安排到其他人的代码库中我已经能够处理到目前为止所抛出的大部分内容但是这个有点过头了。有一些保留周期,我无法弄清楚如何解决。

有一个包装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变量是僵尸。

//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];
}

关于我做错的任何想法?

4 个答案:

答案 0 :(得分:0)

当他们出现时,我被块感到激动,几乎每个界面都写着它们。在我意识到内存管理和可读性问题之后,除非API 真的调用了块,否则我将返回委托。也许这也会让你的代码看起来更好,如果没有太多的代码可以改变。

答案 1 :(得分:0)

首先,如果你正在重新编写代码库,我建议借此机会转向ARC。它会让生活变得更容易。

如果您不得不对所有代码进行筛选,那么它可能是值得的......并且ARC自动化转换工具非常好。

尽管如此,你会看到保留周期,但ARC加上__weak是打破保留周期的好方法。

除此之外,你将不得不单独处理每个块结构,而不仅仅是外部方法。

答案 2 :(得分:0)

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

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

由于您必须定位iOS4,我认为最好的选择是将回调移动到方法而不是属性。在这种情况下,保留周期仍然存在,但它的范围很窄,只能执行underlyingRequest。一旦执行并释放completionBlockfailedBlock,就可以释放SHRequest(一旦没有其他东西保留它)。块属性的常见问题是self保留了块,它保留了self,并且打破循环的唯一方法是在块中使用对self的弱引用,或者在某些时候明确地将属性清零,这可以在回调场景中很难做到。这里大致是使用返回回调块的方法的代码:

-(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

答案 3 :(得分:0)

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

-(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并保留它然后在finish / fail块中释放它

__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];
};

这样仪器不再报告泄漏。