@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 方法,之所以这样分析,就是想对这个过程有个了解,不足之处,敬请指出...

posted @ 2015-01-24 12:01  那年阳光灿烂  阅读(302)  评论(0编辑  收藏  举报