首页 > 代码库 > 循环引用 -- id 为什么是 assign 而不是 retain
循环引用 -- id 为什么是 assign 而不是 retain
什么是循环引用
简单来说,循环引用就是: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 =12 UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:aViewController];//A +1 +1 =23 self.window.rootViewController = navigationController; // A +1+1 +1 = 34 [aViewController release]; // A 3 -1 = 25 [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 ZYAViewController16 17 18 - (void)viewDidLoad19 {20 [super viewDidLoad];21 22 }23 24 - (IBAction)goToNext:(id)sender25 {26 ZYBViewController *bViewController = [[ZYBViewController alloc] init]; // B +127 bViewController.delegate = self; // A 1 +1 = 228 [self.navigationController pushViewController:bViewController animated:YES]; // B 1 +1 = 229 [bViewController release]; // B 2 -1 = 130 }31 32 #pragma mark - 协议方法33 - (void)changeColor34 {35 self.view.backgroundColor = [UIColor redColor];36 }37 38 - (void)dealloc39 {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 @end11 12 // ------------------------------------------------------------------------------13 #import "ZYBViewController.h"14 15 @implementation ZYBViewController16 17 - (void)viewDidLoad18 {19 [super viewDidLoad];20 21 }22 23 - (IBAction)changeABackgroundColor:(id)sender24 {25 [_delegate changeColor];26 }27 28 - (void)dealloc29 {30 [_delegate release]; // A 2 -1 = 131 [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 @end12 13 // -----------------------------------------------------------------------------14 #import "ZYAViewController.h"15 16 @implementation ZYAViewController17 18 19 - (void)viewDidLoad20 {21 [super viewDidLoad];22 23 }24 25 - (IBAction)goToNext:(id)sender26 { // 注意与第一种写法的不同27 _bViewController = [[ZYBViewController alloc] init]; // B +1 = 1 28 _bViewController.delegate = self; // A 1 +1 = 229 [self.navigationController pushViewController:_bViewController animated:YES]; // B 1 +1 = 230 }31 32 #pragma mark - 协议方法33 - (void)changeColor34 {35 self.view.backgroundColor = [UIColor redColor];36 }37 38 - (void)dealloc39 {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,避免出现循环引用的情况。
循环引用 -- id 为什么是 assign 而不是 retain