循环引用 -- 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 =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,避免出现循环引用的情况。

posted @ 2015-01-24 16:06  红红de  阅读(404)  评论(0编辑  收藏  举报