什么是循环引用
简单来说,循环引用就是:A 保留了 B, B 保留了 A, 造成 A 和 B 都不能被释放。
id 为什么是 assign 而不是 retain
从文章标题大概也能猜到, id 是 assign 而不是 retain 的原因是跟你循环引用有关系了。原因也确实如此。
id 之所以是 assign 而不是 retain ,就是因为 retain 可能 会导致循环引用,注意这里的用词是 可能,而不是一定。
下面就用一个实例来说明情况。
注意要严格遵循内存管理的
一、先创建一个协议,协议里有一个方法,方法的功能是改变颜色
1 #import <Foundation/Foundation.h> 2 3 @protocol ZYChangeColorDelegate <NSObject> 4 5 - (void)changeColor; 6 7 @end
二、创建两个视图控制器类
1. 视图控制器 A 和 B ,将两个视图控制器都加进导航,跳转到 B 时,点击 B 上的按钮能够改变 A 的背景色。这里为了说明 id 为什么是 assign 而不是 retain,使用代理传值,其他方法不做讨论。 A 、B 上的按钮都是通过 XIB 实现.
1.1 首先是使用 retain 不会导致循环引用的方式
入口类里部分代码
1 ZYAViewController *aViewController = [[ZYAViewController alloc] init]; // A +1 =1
2 UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:aViewController];//A +1 +1 =2
3 self.window.rootViewController = navigationController; // A +1+1 +1 = 3
4 [aViewController release]; // A 3 -1 = 2
5 [navigationController release]; // A 2 -1 = 1
从入口类开始仔细计算 A 和 B 的引用计数
1 // A 2 #import <UIKit/UIKit.h> 3 4 #import "ZYChangeColorDelegate.h" 5 6 @interface ZYAViewController : UIViewController <ZYChangeColorDelegate> 7 8 @end 9 10 // ---------------------------------------------------------------------------------- 11 12 #import "ZYAViewController.h" 13 #import "ZYBViewController.h" 14 15 @implementation ZYAViewController 16 17 18 - (void)viewDidLoad 19 { 20 [super viewDidLoad]; 21 22 } 23 24 - (IBAction)goToNext:(id)sender 25 { 26 ZYBViewController *bViewController = [[ZYBViewController alloc] init]; // B +1 27 bViewController.delegate = self; // A 1 +1 = 2 28 [self.navigationController pushViewController:bViewController animated:YES]; // B 1 +1 = 2 29 [bViewController release]; // B 2 -1 = 1 30 } 31 32 #pragma mark - 协议方法 33 - (void)changeColor 34 { 35 self.view.backgroundColor = [UIColor redColor]; 36 } 37 38 - (void)dealloc 39 { 40 [super dealloc]; 41 } 42 43 @end
1 // B 2 #import <UIKit/UIKit.h> 3 4 #import "ZYChangeColorDelegate.h" 5 6 @interface ZYBViewController : UIViewController 7 8 @property (nonatomic, retain) id <ZYChangeColorDelegate> delegate; 9 10 @end 11 12 // ------------------------------------------------------------------------------ 13 #import "ZYBViewController.h" 14 15 @implementation ZYBViewController 16 17 - (void)viewDidLoad 18 { 19 [super viewDidLoad]; 20 21 } 22 23 - (IBAction)changeABackgroundColor:(id)sender 24 { 25 [_delegate changeColor]; 26 } 27 28 - (void)dealloc 29 { 30 [_delegate release]; // A 2 -1 = 1 31 [super dealloc]; 32 } // 当点击导航栏上的返回按钮,即将 B 从导航控制器 pop 出来,引用计数的变化为 B 1 -1 = 0, 此时会调用 B 的 dealloc 方法,将 A 的引用计数减 1,当 A 也从导航中 pop 出来(这里 A 是根属兔控制器,之恩个等到程序结束了)时,引用计数为 A 1 -1 = 0,会调用 A 的 dealloc 方法。
上面的这种情况是不会导致循环引用的,因为 A 和 B 的引用计数都在适当的时候变为 0 ,都能够调用各自的 dealloc 方法。
下面是回引起循环引用的情况
2. 使用 retain 导致循环引用,值需要将 A 稍加修改:在 A 中添加一个属性(或者成员变量)
1 // A 2 #import <UIKit/UIKit.h> 3 4 #import "ZYChangeColorDelegate.h" 5 #import "ZYBViewController.h" 6 7 @interface ZYAViewController : UIViewController <ZYChangeColorDelegate> 8 9 @property (nonatomic, retain) ZYBViewController *bViewController; 10 11 @end 12 13 // ----------------------------------------------------------------------------- 14 #import "ZYAViewController.h" 15 16 @implementation ZYAViewController 17 18 19 - (void)viewDidLoad 20 { 21 [super viewDidLoad]; 22 23 } 24 25 - (IBAction)goToNext:(id)sender 26 { // 注意与第一种写法的不同 27 _bViewController = [[ZYBViewController alloc] init]; // B +1 = 1 28 _bViewController.delegate = self; // A 1 +1 = 2 29 [self.navigationController pushViewController:_bViewController animated:YES]; // B 1 +1 = 2 30 } 31 32 #pragma mark - 协议方法 33 - (void)changeColor 34 { 35 self.view.backgroundColor = [UIColor redColor]; 36 } 37 38 - (void)dealloc 39 { 40 [_bViewController release]; 41 [super dealloc]; 42 } 43 44 @end
现在在从入口类开始计算 A、B 的引用计数:此时但从 B 界面离开的时候,B 从导航中 pop 出来,B 的引用计数减 1 ,变为 1,dealloc 方法是在 其引用计数为 0 的时候才会调用,所以此时 A 就不能得到一次 释放,引用计数为 2 ,当 A pop 出导航的时候,其引用计数为 1,也不能调用 dealloc 方法。所以 A 和 B 都不能被释放,这样就形成了这样一种形式:
B 要释放自己 出栈后发现自己引用计数为 1 然后发现只要 A 的 dealloc 方法执行了 B 就可以释放了 找 A 去
A 想要执行自己的 dealloc 方法 要释放自己 发现如果导航释放了自己,自己的引用计数还为 1 然后发现只要 B 的 dealloc 方法执行了 A 就可以释放了 找 B 去
这样就造成了因为循环引用,双方都不能释放自己。
三、使用 assign 避免循环引用
使用 assign,不会使 引用计数 加 1,避免出现循环引用的情况。