Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/98.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何打破代码中的保留周期?_Ios_Memory Leaks_Objective C Blocks - Fatal编程技术网

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

Ios 如何打破代码中的保留周期?,ios,memory-leaks,objective-c-blocks,Ios,Memory Leaks,Objective C Blocks,最近我被扔进了别人的代码库,我已经能够处理到目前为止它抛出的大多数事情,但这一个有点超出了我的头脑。有些保留周期我不知道如何修复 有一个自定义对象包装FROAuthRequest,FROAuthRequest有一个完成块,其中使用了另外3个块,一个解析块、完成块和失败块。完成、完成和失败块都会导致保留周期 我知道原因是块中引用了ivar,但我尝试的方法没有奏效,请参阅文章末尾了解我尝试的方法 以下代码与我开始尝试修复它之前的代码相同。代码路径如下所示: 1:创建请求: //in the Main

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

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

我知道原因是块中引用了ivar,但我尝试的方法没有奏效,请参阅文章末尾了解我尝试的方法

以下代码与我开始尝试修复它之前的代码相同。代码路径如下所示:

1:创建请求:

//in the MainViewController.m
SHRequest *request = [api userInfo];
2:创建怪物任务的方法

//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];
}
//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;
}
3:史莱克斯

//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];
}
//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,就会设置finish和fail块。对于这种情况,未设置故障块

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

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

    //more code

};
[request send];
这是我试过的 我将completionBlock代码移动到一个方法,该方法启动请求并使用_块类型,泄漏似乎已经消失,但当完成块运行时,一些_块变量是僵尸

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

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

当Block出现并编写了几乎所有与它们相关的界面时,我非常激动。之后,我意识到了内存管理和可读性方面的问题,这让我回到了委托,除非API真的调用块。如果没有太多的代码需要修改的话,也许这也会让你的代码看起来更好。

当代码块出现并用它们编写几乎所有的接口时,我被它们吓了一跳。之后,我意识到了内存管理和可读性方面的问题,这让我回到了委托,除非API真的调用块。如果没有太多的代码需要更改,那么这也可能会使您的代码看起来更好。

首先,如果您正在重新编写代码库,我建议您抓住机会转到ARC。这将使街区的生活更加轻松

如果你无论如何都要筛选所有的代码,这可能是值得的。。。电弧自动转换工具非常好

尽管如此,您仍将看到保留周期,但ARC plus __; weak是打破保留周期的好方法


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

首先,如果您正在重新编写代码库,我建议抓住机会移动到ARC。这将使街区的生活更加轻松

如果你无论如何都要筛选所有的代码,这可能是值得的。。。电弧自动转换工具非常好

尽管如此,您仍将看到保留周期,但ARC plus __; weak是打破保留周期的好方法


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

如果您获得EXC\u BAD\u访问,那么要么在完成块运行时SHRequest对象已消失,要么某些代码过早地清除了完成块属性。如果您的目标是iOS 5,则可以通过使块变量成为弱引用来解决此问题,这样,如果SHRequest对象的生命周期在回调之前结束,它们就不会被调用:

__weak void(^fail)(void) = failBlock;
由于您必须以iOS4为目标,我认为最好的选择是将回调移到方法而不是属性中。在这种情况下,retain循环仍然存在,但其范围仅限于执行underlyingRequest。一旦它执行并释放completionBlock和failedBlock,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];
}

顺便说一句,如果您正在获得EXC\u BAD\u访问权限,那么要么在完成块运行时SHRequest对象已消失,要么某些代码过早清除了完成块属性的可能性较小。如果您的目标是iOS 5,则可以通过使块变量成为弱引用来解决此问题,这样,如果SHRequest对象的生命周期在回调之前结束,它们就不会被调用:

__weak void(^fail)(void) = failBlock;
由于您必须以iOS4为目标,我认为最好的选择是将回调移到方法而不是属性中。在这种情况下,retain循环仍然存在,但其范围仅限于执行underlyingRequest。一旦它执行并释放completionBlock和failedBlock,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];
}

顺便说一句,复制解析/完成/失败块并将请求对象作为块的参数传递似乎解决了我的问题

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

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

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

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

这样,Instruments就不会报告更多的泄漏。

我已经考虑过这一点,但这将需要对代码进行大规模重构。问题中的请求只是众多请求中的一个,每个请求在解析块中需要不同的代码。本质上,SHRequest类是应用程序的核心,对服务器的每个请求都要经过它。如果这是唯一的解决方案,我会这样做,但这段代码已经在市场上流行,所以我希望有人能提供一个不需要耗时重构的解决方案。块只是一些语法糖的对象,内存管理与以前没有什么不同。对于带有块的代理,可以执行与以前完全相同的操作。唯一的区别是,代表通常被弱引用所持有;如果要对块执行相同的操作,则需要使用_block或_weak.block保存对self的弱引用。块不仅仅是对象,因为它们捕获了它们的环境。我不认为在“普通”Objective-C中可以做到这一点。这就是为什么我说使用块的内存管理有点不同——创建一个API非常容易,它可以让您始终像在本例中一样处理保留周期。有些循环最终会自动中断,而另一些则必须手动中断,比如弱自锯齿。这通常是笨拙的,这就是为什么我说我更喜欢委托,因为委托通常会给你更干净的代码和更容易的内存管理=API win。我已经考虑过这一点,但这将需要大量的代码重构。问题中的请求只是众多请求中的一个,每个请求在解析块中需要不同的代码。本质上,SHRequest类是应用程序的核心,对服务器的每个请求都要经过它。如果这是唯一的解决方案,我会这样做,但这段代码已经在市场上流行,所以我希望有人能提供一个不需要耗时重构的解决方案。块只是一些语法糖的对象,内存管理与以前没有什么不同。对于带有块的代理,可以执行与以前完全相同的操作。唯一的区别是,代表通常被弱引用所持有;如果要对块执行相同的操作,则需要使用_block或_weak.block保存对self的弱引用。块不仅仅是对象,因为它们捕获了它们的环境。我不认为在“普通”Objective-C中可以做到这一点。这就是为什么我说使用块的内存管理有点不同——创建一个API非常容易,它可以让您始终像在本例中一样处理保留周期。有些循环最终会自动中断,而另一些则必须手动中断,比如弱自锯齿。这通常很笨拙,这就是为什么我说我更喜欢委托,因为委托通常会给你更干净的代码和更容易的内存管理=API win。你能告诉我failBlock、finishBlock和parsingBlock是如何声明的吗?还有,这是弧吗?不,它不是弧,问题中的完成和解析块已经在那里了。解析见第2点,完成见第4点。所以听起来这些块在被访问之前就被释放了。是否对属性使用“复制所有权”修改器?声明应类似于@propertycopyid^parsingBlockFROAuthRequest*finishedRequest;是的,它被复制了,在块执行之前发生了一个自动释放,因为SHRequest对象作为一个自动释放对象从一个方法返回,但我不明白这怎么可能是原因…好的,还有一个问题,你是支持iOS 4,还是仅仅支持5+?你能发布failBlock,finishBlock,和parsingBlock是否已声明?还有,这是弧吗?不,不是弧,完成和解析块已经在问题中了。参见第2点的解析和第4点的完成。所以听起来这些块好像是在被释放之前被释放的
访问。是否对属性使用“复制所有权”修改器?声明应类似于@propertycopyid^parsingBlockFROAuthRequest*finishedRequest;是的,它被复制了,在块执行之前发生了一个自动释放,因为SHRequest对象是作为一个自动释放对象从一个方法返回的,但我不明白这怎么可能是原因…好的,还有一个问题,你是支持iOS 4,还是仅仅支持5+?