如果一个类含有很多的实例变量,那么我们将会为这些实例变量编写几乎是一样的setter和getter方法,对于写程序来说这样的重复编码当然是不合理的。所以在oc 2.0中苹果引入了属性,它组合了新的预编译指令和新的访问器语法,可以很好的解决编写实例变量访问方法的问题。
我们来看一个实际的例子,类AllWeatherRadial包含了两个float类型的实例变量,如果没有使用属性的话它的接口代码是这样的:
@interface AllWeatherRadial : Tire
{
float rainHandling;
float snowHandling;
}
-(void) setRainHandling:(float) rainHandling;
-(float)rainHandling;
-(void) setSnowHandling:(float) snowHandling;
-(float) snowHandling;
@end;
这样的声明了接口后,在类的实现部分我们将会把实例变量rainHandliing和snowHandliing的访问方法都实现出来,这样写的确挺繁琐的。
接下来我们来看看使用属性的接口代码:
@interface AllWeatherRadial : Tire
{
float rainHandling;
float snowHandling;
}
@property float rainHandling;
@property float snowHandling;
@end // AllWeatherRadial
这样的写法就要比原来的方法好多了,最起码少敲了很多代码。这里的@property是一种新的编译器功能,它意味着声明了一个新的对象的属性。 而@property预编译指令的作用就是自动声明属性的setter和getter方法; @property float rainHandling;语句表明AllWeatherRadial类的对象具有float类型的属性,其名称为rainHandling。同时你也可以通过调用-setRainHandling:来设置属性,调用-rainHandling来访问属性。在这里属性的名称与实例变量的名称是一样的,当然我们也可以让它们不一样。稍后再讨论这个问题。
接下来我们看看使用属性后的类实现代码:
@implementation AllWeatherRadial
@synthesize rainHandling;
@synthesize snowHandling;
@end;
这里我们只讨论类的访问方法,所以类的其他代码并没有贴出来,类AllWeatherRadial的初始化方法和其他的一些方法都是一样的。这里的@synthesize也是一种新的编译器功能,它表示创建了该属性的访问代码。比如:@synthesize rainHandling;这行代码表示编译器将添加rainHandling实例变量的访问方法。注意这里我们是把实例变量的声明放在了头文件得接口代码中,当然如果你不声明这些变量(就是上面接口代码大括号中的部分)编译器也会声明的。实例变量的声明我们可以放在两个地方:头文件和实现文件中,甚至还可以拆开来一部分放在头文件中声明,一部分放在实现文件中声明。但是这样有什么区别呢? 情况是这样的,如果你声明的类有一个子类,并且想要直接从子类中通过属性来访问变量,那么变量的声明就必须放在头文件中。如果你确定变量只属于当前类则可以把其声明放在实现文件中。如下:
@implementation AllWeatherRadial
{
float rainHandling;
float snowHandling;
}
@synthesize rainHandling;
@synthesize snowHandling;
@end;
上面我们说了属性的名称可以实例变量的名称不一样,那应该怎么实现呢?如下代码所示:
@interface AllWeatherRadial : Tire
{
float rainHandling;
float snowHandling;
}
@property float propertyName; //注意这里
@property float snowHandling;
@end // AllWeatherRadial
//在实现文件中也要做相应的修改
@synthesize propertyName = rainHandling;
这样修改了后编译器仍将创建-setPropertyName:和-propertyName方法,但在其实现代码中用的却是rainHandling实例变量。
接下来我们来看看属性中的点表达式,我们都知道在C或者C++中可以直接通过点(.)来访问struct结构体中的变量,而且C++中对象还可以直接通过点(.)来访问对象中的方法。而在OC中的点(.)表达式只需明白一点,OC中的点表达式是为了更好的简化代码而采用的新特性,点表达式实质上是在调用实例变量的访问方法。如果点表达式出现在等号(=)的左边,那么该变量的setter方法将被调用,如:tire.rainHandling = 20.0;实际上是在调用tire对象中的-setHandling:方法。如果点表达式出现在了等号的右边,则调用的是变量的getter方法,如:float number = tire.rainHandling;实际上是调用了tire对象的-rainHandling方法。关于点表达是在使用的时候还有一些需要注意的地方,我们接着往下看。
在前面我们讨论的是float类型的属性,这样的使用方法也适用于int char BOOL和struct等类型。但是当我们在使用访问方法来访问变量的时候需要保留和释放对象,而且还有一些对象,比如字符串的值我们想要复制(-copy)它们。我们先看下面的代码:
@class Tire;
@class Engine;
@interface Car : NSObject
{
NSString *name;
NSMutableArray *tires;
Engine *engine;
}
@property (readwrite, copy) NSString *name;
@property (readwrite, retain) Engine *engine;
- (void) setTire: (Tire *) tire
atIndex: (int) index;
- (Tire *) tireAtIndex: (int) index;
- (void) print;
@end // Car
在OC的代码中我们经常都会看到在@property 声明属性那里加了一些相关的特性,这些特性是用来限制属性的。比如这里的name属性就添加了readwrite和copy特性,engine加了readwrite和retain特性,在这里readwrite特性表明编译器会为变量name生成setter和getter方法,当然如果是加了readonly 就只会生成getter方法了。我们重点要说的是这里的copy和retain特性,如果不写的话默认的会是assign特性。
那么这里的copy retain assign都有些什么区别呢?首先assign特性是默认的,指定setter方法采用简单的赋值,比如对一些float int 变量是没有问题的,因为只是简单的赋值所以不会更改变量对应的引用计数,所以assign:就是简单赋值,不更改索引计数(Reference Counting).使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等),如果变量是对象指针的话就不行了。retain和copy在内存管理中说过它们会自动增加引用计数(+1)。retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 ,使用retain: 对其他NSObject和其子类 ,retain,是说明该属性在赋值的时候,先release之前的值,然后再赋新值给属性,引用再加1。copy:指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。copy: 建立一个索引计数为1的对象,然后释放旧对象,copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。当然还有一些其他的特性,如:atomic,nonatomic等,指定原子和非原子特性,同样默认是atomic。retain的做法就类似这样:
-(void) setEngine:(Engine *)newEngine
{
[newEngine retain];
[engine release];
engine = newEngine;
}
对于对象的属性在使用的时候应该特别注意一点,比如在上面接口代码中name属性,name = @"Car" 和self.name = @“Car” 这两个语句有什么区别呢?在使用的时候如果我们用的是前者,也就是没有self点表达式的,那么编译器会认为我们是在给一个名为name的变量进行简单的赋值,当然引用计数是没变的。而采用的是或者带self的语句的话,就表明我们是在调用name的setter方法,也可以写成这样[self setName: @"Car"],这就是点点表达式的妙用。所用在具体使用的时候我们也特别注意一下。还有比较重要的一点:别忘了内存管理,在-dealloc方法中该释放的得记得释放。
参考书籍:《Objective-C 基础教程(第二版)》