block块使用不当导致的循环引用与内存泄漏
注:本文所用代码都是在MRC下
问题场景
首先我们来看下面一段代码,思考一下,会出现什么问题?
UINavigationController *navi = (UINavigationController *)[_mainTabBar selectedViewController];
id<UINavigationControllerDelegate> navigatVCDelegate = navi.delegate;
IFundViewController *fundViewController = [[IFundViewController alloc]initWithUserID:strUserid andParams:params];
[fundViewController setControllerStackBehavior:^{
navi.delegate = navigatVCDelegate;
[navi popViewControllerAnimated:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:NT_VIPUSERLOGIN object:self];
fundViewController.hidesBottomBarWhenPushed = NO;
navi.navigationBarHidden = NO;
}];
[fundViewController setHidesBottomBarWhenPushed:YES];
[fundViewController pushSDKUseNavigation:navi];
navi.delegate = nil;
分析问题
经过观察,我们可以发现这段代码存在以下问题:
fundViewController
未被释放,出现内存泄漏.block
块中出现循环引用.
那么我们就来分析一下这些问题是怎么出现的,以及该怎么解决这个问题
1.内存泄漏
在MRC下,所有的内存都是由编码者自己手动管理,依据的原则就是谁管理谁释放,很明显的,我们看到我们通过alloc
创建对象以后,并没有在使用完之后进行release
操作,这就会造成内存泄漏.
2.循环引用
fundViewController
所持有的block
块在使用的过程中会对内部的对象进行持有,也就是说这个block
块对fundViewController
和navi
两个对象都持有一份.
那这里就会出现两个循环引用:
fundViewController
对block
块持有一份,block
块又对fundViewController
持有一份.也即是block
块与fundViewController
相互持有.block
块对navi
持有一份,navi
对fundViewController
持有一份,fundViewController
对block
持有一份.也即是block
块与navi
相互持有.
注:也正是因为这种循环引用的出现,我们即使在使用完fundViewController时进行release操作还是会导致内存泄漏,因为循环引用,对象一直被持有,就会释放不掉.也就是说在上段代码中,我们必须先解决循环引用的问题才能解决内存泄漏的问题.
那怎么解决这个问题呢?既然出现了循环引用,我们肯定是想打破这种循环引用,在MRC下打破这种循环引用就要使用__block
(ARC下使用__weak
)来修饰block块中使用的对象.这样在block中使用时就不会对外面的变量进行持有,也就是打破了这种循环引用.
解决问题
经过上面的分析,代码改成如下就会解决以上两个问题.
__block UINavigationController *navi = (UINavigationController *)[_mainTabBar selectedViewController];
id<UINavigationControllerDelegate> navigatVCDelegate = navi.delegate;
__block IFundViewController *fundViewController = [[IFundViewController alloc]initWithUserID:strUserid andParams:params];
[fundViewController setControllerStackBehavior:^{
navi.delegate = navigatVCDelegate;
[navi popViewControllerAnimated:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:NT_VIPUSERLOGIN object:self];
fundViewController.hidesBottomBarWhenPushed = NO;
navi.navigationBarHidden = NO;
}];
[fundViewController setHidesBottomBarWhenPushed:YES];
[fundViewController pushSDKUseNavigation:navi];
navi.delegate = nil;
[fundViewController release];