2

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

有一个包装 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

4 回答 4

0

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

于 2012-07-12T14:04:34.747 回答
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 并保留它,然后在完成/失败块中释放它

__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 就不再报告泄漏。

于 2012-08-10T11:50:47.237 回答
0

If you're getting EXC_BAD_ACCESS, then either the SHRequest object is gone by the time the completion block runs, or (perhaps less likely) some code has prematurely cleared out your completion block properties. If you were targeting iOS 5, you could solve it by making the block variables weak references, so they wouldn't be invoked if the SHRequest object's lifecycle ends before the callback:

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

Since you have to target iOS4, I think the best option is to move the callbacks into methods instead of properties. In this case, the retain cycle still exists, but it's narrowly scoped to the execution of the underlyingRequest. Once it executes and releases completionBlock and failedBlock, the SHRequest can be deallocated (once nothing else is holding onto it). The common problem with block properties is that self retains the block, which retains self, and the only way to break the cycle is to either use a weak reference to self in the block, or explicitly nil out the properties at some point, which can be hard to do in a callback scenario. Here's roughly what your code would look like with methods returning the callback blocks:

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

Incidentally, there are some other good reasons for returning callback blocks from methods.

于 2012-07-16T19:53:57.813 回答
0

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

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

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

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

于 2012-07-12T17:59:13.287 回答