本文受Ash Mike的《The complete Friday Q&A:Volume I》的启发所编写。
accessor最大的优点是在数据访存安全性的保证,同时还可以完成内存管理功能,对于override的accessor还加入了一些自定义的功能,非常便捷。但是反对者也提出来在init和dealloc中使用accessor所带来的副作用。苹果官方文档也建议不要在这里面使用accessor。
Ash Mike提出了一种场合,在init和dealloc中使用accessor不慎,可能导致的问题:
假设我们在子类里覆盖了父类的setter方法,并在里面使用父类的setter方法对父类完成一些初始化或者销毁变量的操作。下面的例子看起来无害,却有一些潜在的问题:
-(void)setSomeObj:(id)obj
{
[anotherObj notifySomething];
[super setSomeObj:obj];
}
-(void)dealloc
{
[anotherObj release];
[super dealloc];
}
假如父类使用accessor销毁someObj,那么覆盖的setter会在dealloc执行完之后被调用,但是此时anotherObj已经被release,这样就潜在问题,也就是anotherObj指向一块不能保证安全性的区域,是个dangling reference。虽然anotherObj指向的区域不会立即被销毁或者被别的对象所使用,但是是潜在的即将被回收使用的。出于安全的考虑,我们应当在[anotherObj release];之后将其设置为nil:anotherObj = nil;。
至于整个过程是怎么回事的,就涉及面向对象的基本知识了。当子类release之后,就会调用子类的dealloc,anotherObj被release,然后调用父类的dealloc,先前我们说父类的dealloc应该类似这样:
-(void)dealloc
{
[self setSomeObj:nil];
[super dealloc];
}
这里应该注意的是父类调用的setSomeObj:是被子类覆盖的那个,因此anotherObj 又执行一个方法,当然这是不安全的,可能会导致crash,假如像前面那样设置为nil的话,这里相当于什么都没做。接着再执行super 的setSomeObj:方法,这时候才是调用父类自己的setter。setter的合成写法大概类似下面:
-(void)setSomeObj:(id)obj
{
[obj retain];
[someObj release];
someObj = obj;
}
这样会先对nil进行retain,当然是什么事都不做,接着对init方法中retain的对象release,最终someObj = nil。整个过程没有内存泄露,问题的关键还是在于setter覆盖造成的一些不那么明显的危险。
有兴趣的同学可以试着写出下面main.h的运行结果,加深理解。
#import <Foundation/Foundation.h>
@interface Father : NSObject
@property(retain)NSString *firstname;
@end
#import "Father.h"
@implementation Father
@synthesize firstname = _firstname;
-(id)init
{
self = [super init];
if (self) {
[self setFirstname:@"Green"];
}
return self;
}
-(void)dealloc
{
NSLog(@"Father:%@",NSStringFromSelector(_cmd));
[self setFirstname:nil];
[super dealloc];
}
@end
#import "Father.h"
@interface Son : Father
@property(retain)NSString *secondName;
@end
#import "Son.h"
@implementation Son
@synthesize secondName = _secondName;
-(id)init{
self = [super init];
if (self) {
_secondName = @"Bload";
}
return self;
}
-(void)setFirstname:(NSString *)firstname
{
NSLog(@"second name length is%ld",(unsigned long)[_secondName length]);
[super setFirstname:firstname];
NSLog(@"Son:%@",NSStringFromSelector(_cmd));
}
-(void)dealloc
{
NSLog(@"Son:%@",NSStringFromSelector(_cmd));
[_secondName release];
//_secondName = nil;
[super dealloc];
}
@end
#import <Cocoa/Cocoa.h>
#import "Son.h"
int main(int argc, char *argv[])
{
Son *jack = [[Son alloc]init];
NSLog(@"====================");
NSLog(@"jack's first name is %@,second name is %@",jack.firstname,jack.secondName);
NSLog(@"====================");
[jack setFirstname:@"hil"];
NSLog(@"====================");
[jack release];
return 0;
}
运行结果:
2014-02-24 23:45:56.074 test[1908:303] second name length is0
2014-02-24 23:45:56.115 test[1908:303] Son:setFirstname:
2014-02-24 23:45:56.117 test[1908:303] ====================
2014-02-24 23:45:56.118 test[1908:303] jack's first name is Green,second name is Bload
2014-02-24 23:45:56.159 test[1908:303] ====================
2014-02-24 23:45:56.159 test[1908:303] second name length is5
2014-02-24 23:45:56.163 test[1908:303] Son:setFirstname:
2014-02-24 23:45:56.164 test[1908:303] ====================
2014-02-24 23:45:56.165 test[1908:303] Son:dealloc
2014-02-24 23:45:56.179 test[1908:303] Father:dealloc
2014-02-24 23:45:56.180 test[1908:303] second name length is5 //设置nil时 为second name length is0
2014-02-24 23:45:56.182 test[1908:303] Son:setFirstname: