对于对象类型,一般在声明属性的时候,都会将属性的特性设置为 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 方法,之所以这样分析,就是想对这个过程有个了解,不足之处,敬请指出...