属性的使用
属性的使用限制
任何Objective-C中的类,核心基本数据类型(Core Foundation data type)或者叫做基本数据类型(POD)都可用于属性。更多“核心基本数据类型”的使用限制请参阅“核心基础”章节。
属性的重声明
我们可以在派生类中对属性进行重新声明,但是必须在整个派生类中重新描述属性(Property)的属性(attributes)。这种规则同样适用于类别或者协议中的属性重声明。
如果我们在某个类中声明的属性为只读(readonly)型,那么在对该类的扩展,协议或者派生类中是可以重新将其声明为可读写(readwrite)的。就类的扩展而言,凡是在@synthesize语句之前的对属性的重声明都会导致将编译器按照新的声明来对属性的访问方进行自动生成。这种可以把只读属性重新声明为可读写属性使得以下两种类的实现模式成为可能:不能修改(immutable)的类可以派生出可以修改的类(mmutable)以及类的属性对外可以是只读的而对内部则是可以读写的。下面的代码段就展示了如何针对外部API将属性设置为只读的,而对内部实现是可读写的:
//对外共有的头文件
@interface MyObject : NSObject
{
NSString * language;
}
@property (readonly, copy) NSString * language;
@end
//私有的实现文件
@interface MyObject()
@propert (readwrite, copy) NSString *language;
@end
@implementation MyObject
@synthesize language;
@end
拷贝
如果在声明属性的时候使用了copy,那就是说在给属性赋值的时候将采用拷贝的方式。如果此时使用@synthesize来自动生成属性的访问方法,那么setter中将使用的是copy方法。当传给setter的参数有可能是可修改对象,而我们想确保该类持有的是不可修改的对象时,这种拷贝的方式就显得非常有用。例如,如果我们对属性的声明如下:
@property(nonatomic, copy) NSString *string;
那么,编译器自动生成的setter方法将类似与下面的代码:
-(void) setString: (NSString *) newString
{
if ( string != newString )
{
[string release];
string = [newString copy];
}
}
尽管上面的这种方式对字符串来说能够很好的工作。但是如果是数组(array)或者集合(set),那么上面的方式可能存在问题。比如,我们希望属性是可以被修改的,然而copy方法返回的确实一个不可被修改的对象。此时,我们就应该自行实现属性的setter方法了。如下:
@interface MyClass : NSObject
{
}
@property ( nonatomic, copy ) NSMutableArray * myArray;
@end;
@implementation MyClass
@sysnthesize myArray;
-(void) setMyArray: (NSMutableArray *) newArray
{
if (myArray != newArray )
{
[myArray release];
myArray = [newArray MutalbeCopy];
}
}
属性与dealloc方法
当使用@synthesize关键字自动生成属性的访问方法时,编译器会自动根据需要创建访问方法。然而,没有指令来完成属性的声明和dealloc方法的交互。也就是说属性是不会被自动释放的。但是,属性的声明的确为我们检查dealloc方法的正确实现提供了一种有效的方法:查看头文件中对所有属性的声明,确保所有非assign方式的属性都要被释放(released);所有assign方式的属性都不应该被释放。
注意:通常在dealloc方法中我们应该直接释放属性对象,而不是调用其setter方法,传入参数nil。如下:
如果你使用的是现代的运行时系统并且对属性进行了@systhesize,那么是不允许直接访问属性对象的,此时必须调用其访问方法来完成对其的释放。如下:
|
核心基础
正如在“属性的属性”小节中提到的那样,在Mac OS X V0.6以前的版本中是不允许设置对非对象类型的retain属性的。因此,如果如果声明了CFType类型的属性,并采用如下的方式来自动生成其访问方法:
@interfaceMyClass :NSObject
{
CGImageRefmyImage;
}
@property(readwrite)CGImageRefmyImage;
@end
@implementationMyClass
@synthesizemyImage;
@end
那么在引用计数的环境下,自动生成的setter方法只是简单地将值赋值给实例变量(新的值没有被retian,旧的值也没有被released)。对于核心基础对象来说,这种简单的赋值是不争气的。因此,我们不应使其自动生成访问方法;而是应该自己实现访问方法。
在垃圾回收机制下,如果图像变量被声明为__strong;
...
__strong CGImageRef myImage;
...
@property CGImageRef myImage;
此时,@synthesize指令生成的访问方法是正确的---上面示例中的图像不是通过CFRetain来retian的,而是自动生成的setter方法触发了一个写屏障。
示例:声明属性与自动生成访问方法
列表5-4中的程序展示了属性的几种不同用法:
- 属性next遵循Link协议
- 类MyClass采纳Link协议,并明确声明了属性next。同时还声明了一些其他的属性
- 使用指令@synthesize来自动生成creationTimestamp和next的访问方法,同时为他们绑定了不同名称的实例变量
- 使用指令@synthesize来自动生成name的访问方法,但是绑定的是同名的实例变量(这点在旧的运行时上是不支持的。)
- 使用指令@dynamic来动态指定gratuitouseFloat属性的访问方法
- 对于属性nameAndAge,并没有使用@dynamic指令。但是这是缺省的做法。这点是通过直接实现其getter方法nameAndAgeAsString来实现的(该属性是只读的)。
@protocol Link
@property (assign)id<Link> next;
@end;
@interface MyClass :NSObject<Link>
{
NSTimeInterval intervalSinceReferenceDate;
CGFloat gratuitousFloat;
id<Link> nextLink;
}
@property(readonly)NSTimeInterval creationTimestamp;
@property(copy)NSString *name;
@property(retain,nonatomic)id<Link> next;
@propertyCGFloat gratuitouseFloat;
@property(readonly,getter = nameAndAgeString)NSString *nameAndAge;
@end
@implementation MyClass
//自动生成属性creationTimestamp和name的setter和getter
//旧的运行是系统不支持如下对name属性使用@synthesize指令
@synthesize creationTimestamp =intervalSinceReferenceDate, name;
//自动生成属性next的setter和getter
@synthesize next =nextLink;
//下面的这条语句也可以没有
@dynamic gratuitouseFloat;
//属性gratuitousFloat的Getter
-(CGFloat) gratuitouseFloat
{
returngratuitousFloat;
}
//属性gratuitousFloat的Setter
-(void)setGratuitouseFloat:(CGFloat)aValue
{
gratuitousFloat = aValue;
}
//只读属性nameAndAge的getter
-(NSString *) nameAndAgeString
{
return [NSStringstringWithFormat:@"",[selfname],
[NSDatetimeIntervalSinceReferenceDate] -intervalSinceReferenceDate];
}
-(id) init
{
self = [superinit];
if (self)
{
intervalSinceReferenceDate = [NSDatetimeIntervalSinceReferenceDate];
}
returnself;
}
-(void) dealloc
{
[nextLinkrelease];
[namerelease];
[superdealloc];
}
@end
列表5-4