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

  • 相关阅读:
    Linux/Unix 新手和专家教程
    恢复Ext3下被删除的文件
    如何调试bash脚本
    8个实用而有趣Bash命令提示行
    使用grep恢复被删文件内容
    一些非常有意思的杂项资源
    chmod -x chmod的N种解法
    纯文本配置还是注册表
    面向对象的Shell脚本
    你可能不知道的Shell
  • 原文地址:https://www.cnblogs.com/hyhl23/p/4246026.html
Copyright © 2020-2023  润新知