@property中 retain 详解

对于对象类型,一般在声明属性的时候,都会将属性的特性设置为 retain 或者 copy, 这与设置成 assign 有什么区别呢?

下面以 Student 类和 MyClass(班级)类为例来说明为什么要设置为 retain 或 copy.

Student类

@interface Student : NSObject
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * sex;

- (id)initWithName:(NSString *)name
               sex:(NSString *)sex;
- (void)sayHi;
@end

现在声明一个 MyClass 类,其中有一个 Student 类型的属性,现将其设置为 assign.

@class Student;
@interface MyClass : NSObject

@property (nonatomic, assign) Student * stu;
@end

MyClass 类中重写 dealloc 方法如下:

- (void)dealloc
{
    [_stu release];
    [super dealloc];
}

当设置为 assign 的时候,编译器自动生成的 setter 方法是这样的

- (void)setStu:(Student *)stu
{
    _stu = stu;     //直接赋值
}

情况一:

在 main.m 中测试如下:

Student * stu = [[Student alloc] initWithName:@"mike" sex:@"M"];
        MyClass * mc = [[MyClass alloc] init];
        [mc setStu:stu];    //stu 直接赋值给 mc 中的 _stu,此时 _stu 和 stu 指向同一块内存,对象 stu 的引用计数为1
        [stu release];      //释放 stu,stu 的引用计数为0,自动销毁
        [mc.stu sayHi];     //此时调用 sayHi 方法,因为 mc 中的 _stu 和 stu 指向的是一块内存,而该块内存已经被释放,因此会造成野指针异常,导致崩溃
        [mc release];

为了避免这种情况的出现,可以考虑将 setter 方法改成这样:

- (void)setStu:(Student *)stu
{
    _stu = [stu retain];    //将原对象 retain 后再赋值.
}

情况二:

在 main.m 中测试如下:

   Student * stu = [[Student alloc] initWithName:@"mike" sex:@"M"];
        MyClass * mc = [[MyClass alloc] init];
        [mc setStu:stu];    //stu 引用计数+1后赋值给 _stu
        [stu release];      //stu 引用计数-1,现在为1
        [mc.stu sayHi];
        [mc release];       //释放 mc, 调用 dealloc 方法,同时释放了 stu.

可以看出,在只为 mc 的实例变量_stu进行一次赋值的时候,这种解决办法是没有问题的,但是当需要为_stu多次赋不同的 Student对象的时候,问题就出来了.

   Student * stu = [[Student alloc] initWithName:@"mike" sex:@"M"];
        MyClass * mc = [[MyClass alloc] init];
        [mc setStu:stu];    //stu 引用计数+1后赋值给 _stu
        [stu release];      //stu 引用计数-1,现在为1
        [mc.stu sayHi];
        Student * stu1 = [[Student alloc] initWithName:@"jimmy" sex:@"F"];
        [mc setStu:stu1];   //将 _stu 赋值为新对象 stu1,stu1的引用计数先+1,此时变为2,此时对象 stu 失去引用.但其引用计数仍为1,内存仍被占用
        [stu1 release];     //stu1 引用计数-1
        [mc release];       //释放 mc, 调用 dealloc 方法,同时释放了 stu1.

可以看出,在改变_stu的值后,每次进行释放 mc, 都只能保证当前_stu所引用的那块内存被释放,而上一次引用的内存将会被保留,当再为_stu赋值 stu2,stu3等对象后,只有最后一次赋值的对象的内存会在释放 mc 的时候得到释放,之前的全部会保留,最终导致内存不够用,程序崩溃.

为了解决该情况,可以考虑在每次为_stu赋值前,先释放_stu之前保存的对象,然后在为其赋值,可以把 setter 方法改为如下:

- (void)setStu:(Student *)stu
{
    [_stu release];         //每次赋值前先释放之前的对象内存
    _stu = [stu retain];    //将原对象 retain 后再赋值.
}

这样做就能保证每次赋新值时都能先释放之前的对象内存,但是,当对 _stu 重复赋同一个对象的时候,就会出现问题.

情况三

在 main.m 中测试如下:

    Student * stu = [[Student alloc] initWithName:@"mike" sex:@"M"];
        MyClass * mc = [[MyClass alloc] init];
        [mc setStu:stu];    //在赋值的时候,先释放属性 _stu 之前的引用,然后 stu 引用+1后赋值给 _stu,此时 stu 引用为2
        [stu release];      //stu 引用-1,为1
        [mc.stu sayHi];
        [mc setStu:stu];    //再次为 _stu 赋值 stu, 由于会在赋值前先 release _stu 之前保存的对象,在这里 _stu 之前保存的时 stu, 因此 stu 引用计数-1,为0,自动调用其 dealloc 方法,销毁对象, stu 已经不存在,然后再将 stu 赋值给 _stu 的时候,就会引用一个不存在的对象,导致异常或者崩溃
        [mc.stu sayHi];
        [mc release];

可以看出,在为_stu第二次赋值的时候,我们再次将stu 赋值给 _stu, 结果导致 stu 在被释放后又要被使用,导致崩溃.为了预防这种情况出现,我们可以在进行赋值前判断一下新赋值的对象是否和 _stu 之前保存的是同一个对象,针对 setter 方法,现在修改如下:

- (void)setStu:(Student *)stu
{
    if (_stu != stu) {          //判断新赋值的对象是否和 _stu 之前保存的相同,若不同,执行赋值操作
        [_stu release];         //每次赋值前先释放之前的对象内存
        _stu = [stu retain];    //将原对象 retain 后再赋值.
    }
}

好了,写了这么多,总算完善了 setter 方法,但是呢,其实只要我们在 MyClass 的. h 文件中,将 属性 stu 的特性设置为 retain, 那么编译器自动为我们生成的 setter 方法就是最后的这个 setter 方法,之所以这样分析,就是想对这个过程有个了解,不足之处,敬请指出...

时间: 2024-07-28 21:15:24

@property中 retain 详解的相关文章

转载:唐磊的个人博客《python中decorator详解》【转注:深入浅出清晰明了】

转载请注明来源:唐磊的个人博客<python中decorator详解> 前面写python的AOP解决方案时提到了decorator,这篇文章就详细的来整理下python的装饰器--decorator. python中的函数即objects 一步一步来,先了解下python中的函数. def shout(word='hello,world'):     return word.capitalize() + '!'print shout()#输出:Hello,world!#跟其他对象一样,你同样

Android中Context详解 ---- 你所不知道的Context

转载至 :http://blog.csdn.net/qinjuning 前言:本文是我读<Android内核剖析>第7章 后形成的读书笔记 ,在此向欲了解Android框架的书籍推荐此书. 大家好,  今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友-----Context类 ,说它熟悉,是应为我们在开发中 时刻的在与它打交道,例如:Service.BroadcastReceiver.Activity等都会利用到Context的相关方法 : 说它陌生,完全是 因为我们真正的不懂Context

Android中Context详解 ---- 你所不知道的Context (转载)

Android中Context详解 ---- 你所不知道的Context (转载) http://blog.csdn.net/qinjuning 大家好,  今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友-----Context类 ,说它熟悉,是应为我们在开发中 时刻的在与它打交道,例如:Service.BroadcastReceiver.Activity等都会利用到Context的相关方法 : 说它陌生,完全是 因为我们真正的不懂Context的原理.类结构关系.一个简单的问题是,一个应用

MySQL中EXPLAIN详解

MySQL中EXPLAIN详解 explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 使用方法,在select语句前加上explain就可以了: 如:explain select username,first_name form hx,itlearner where a.id=b.id EXPLAIN列的解释: id:本次 select 的标识符.在查询中每个 select都有一个顺序的数值. select_type :查询类

Python中dict详解

yangyzh Python中dict详解 python3.0以上,print函数应为print(),不存在dict.iteritems()这个函数. 在python中写中文注释会报错,这时只要在头部加上# coding=gbk即可 #字典的添加.删除.修改操作dict = {"a" : "apple", "b" : "banana", "g" : "grape", "o&qu

winxp计算机管理中服务详解

winxp计算机管理中服务详解01 http://blog.sina.com.cn/s/blog_60f923b50100efy9.html http://blog.sina.com.cn/s/blog_b08c76100102vijm.html winxp计算机管理中服务详解02 http://blog.sina.com.cn/s/blog_60f923b50100efz3.html http://blog.sina.com.cn/s/blog_b08c76100102vijn.html

【转】 java中HashMap详解

原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 H

转:iOS中socket详解

一.网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象: 传输层.会话层.表示层和应用层则被称作主机层,是用户所面向和关心的内容. http协议   对应于应用层 tcp协议    对应于传输层 ip协议     对应于网络层 三者本质上没有可比性.  何况HTTP协议是基于TCP连接的. TCP/IP是传输层协议,主要解决数据如何在网络

Linux系统中目录详解

1.Linux文件系统的层次结构 在Linux或Unix操作系统中,所有的文件和目录都被组织成以一个根节点开始的倒置的树状结构. 文件系统的最顶层是由根目录开始的,系统使用"/"来表示根目录.在根目录之下的既可以是目录,也可以是文件,而每一个目录中又可以包含子目录文件.如此反复就可以构成一个庞大的文件系统. 在Linux文件系统中有两个特殊的目录,一个用户所在的工作目录,也叫当前目录,可以使用一个点"."来表示:另一个是当前目录的上一级目录,也叫父目录,可以使用两个