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