第七章 属性声明

334 阅读8分钟

使用属性编程:

一般来说,属性指的是一个对象的属性或特性。对象的实例变量,也就是访问方法的目标一般被称为属性。

以前的接口文件中使用实例变量和访问方法实现属性的概念,而现在把属性的概念作为一个独立的存在在接口中声明。

属性声明的规则总结如下:

自动生成访问方法

自动生成实例变量

更简单的调用访问方法

属性的内省

属性的概念:

使用属性声明,可以更简洁地实现访问方法。另一方面,不仅仅是访问方法,KVC中所有定义的实例变量都可以被当作属性处理。

显式声明属性:

OC2.0中新增加了属性声明的功能。这个功能可以让编译器自动生成与数据成员同名的方法。

@property int hitPoint;

属性声明等同于声明了读写两个访问方法。

属性声明的时候还可以为属性自定义选项。选项位于圆括号中,前面是@property指令。例如如果想声明一个只读的访问方法,可以在@property后面加上(readonly)。

@property(readonly)  NSString *name;

当两个属性的类型相同时,既可以单独写一行,也可以将他们写一起。例如:

@property int hitPoint, magicPoint;

属性的实现:

通过使用@synthesize,就可以在一行之内自动生成getter和setter方法。将语句应放在@implementation和@end之间,就能自动生成和接口文件中声明的属性一致的访问方法(可读可写或只读)。也可以不使用@synthesize自动生成,而是由自己来实现访问方法。另外我们还可以通过@dynamic关键字告诉编译器合成无效,用户会自己生成getter和setter。

其他方法可以直接在实现文件中实现,而不用在接口文件中声明。但是属性声明的情况下则不允许这种做法。

使用@synthesize的时候,可以在一行中声明多个变量。

通常情况下,@property声明的属性名称和实例变量的名称是相同的,但有时你也可能会需要属性的名称和实例变量的名称不同,这时就可以为实例变量定义其他的属性名称。例如我们可以通过该语句生成名为value的访问方法,并将其绑定到实例变量runningAverage中:

@synthesize value = runningAverage;

可以在类的实现部分中声明一部分或全部实例变量,这种声明方法可以隐藏是否对变量进行了属性声明。另外在子类中访问实例变量时也只能通过访问方法来访问,不能直接访问父类的实例变量。

通过属性声明的方法也能够同访问方法一样实现封装的目的。

给属性指定选项:

可以同时给一个变量指定多个选项,选项之间需要用逗号隔开。

@property 可用选项:

种类

选项

说明

指定方法名

getter = getter方法名

setter = setter方法名显式指定getter方法和setter方法的名字

读写属性

readonly

readwrite只读

读写

赋值时的选项

assign

retain

unsafe_unretained

strong

weak

copy单纯赋值

进行保持操作

同assign一样(用于ARC)

同retain一样(用于ARC)

弱引用(用于ARC)

复制对象

原子性操作

nonatomic

非原子性操作,非线程安全

指定方法名:

可以不使用默认的访问方法名,而通过setter option来指定访问属性用的方法名。例:

@property(setter = setValue)int hitPoint;

可以通过点运算符来调用 .hitPoint,但实际上启动的方法是setValue

读写属性:

readwrite表示属性是可读写的,这也是默认选项。

赋值时的选项:

六个选项之间是排他关系。unsafe_unretained和strong主要被用在ARC的情况下,分别和assign和retain具备同样的功能。

基础数据类型

对象类型:手动引用计数

对象类型:ARC

对象类型:垃圾回收

未指定任何选项

直接赋值

警告

警告

直接赋值(有可能出现警告)

assign

unsafe_unretained直接赋值

直接赋值

直接赋值

直接赋值

retain

strong出错

赋值并对新值进行retain

赋值并对新值进行retain

无特别操作,和assign动作相同

weak

出错

无特别操作,和assign动作相同

弱引用

无特别操作,和assign动作相同

copy

出错

赋值时建立传入值的一份副本

赋值时建立传入值的一份副本

赋值时建立传入值的一份副本

属性是对象类型,且使用了垃圾回收管理内存,有一点需要注意,对于符合NSCopying协议也就是说可以利用copy方法的类实例变量,如果不指定任何选项,就会提示警告。

原子性:

原子性是多线程中的一个概念,如果说访问方法是原子的,那就意味着多线程环境下访问属性是安全的在执行过程中不可被打断。而nonatomic则正好相反,意味着方法在执行时可被打断,缺省情况下访问方法是原子的。

通常不需要指定nonatomic选项,因为这样的机制能提高访问的安全性。但毕竟lock和unlock操作对性能有影响,因此,对于使用频繁且不用考虑多线程竞争的访问方法可以在声明的时候加上nonatomic。

nonatomic选项不仅能被用于使用@synthesize生成的访问方法,手动定义的访问方法中不存在多线程竞争的情况下,也可以给属性加上nonatomic。

属性声明和继承:

子类可以使用父类中定义的属性,也可以重写父类中定义的访问方法,但是,父类中属性声明时指定的各种属性(assign等),或者为实例变量指定的getter和setter的名称等必须完全一样。唯一一个特别的情况是,对于父类中被定义为readonly类型的属性,子类中可以将其变为readwrite。

属性的声明可能会包含范畴或协议,这种情况下实现文件中不可以使用@synthesize,原因是范畴和协议都和实例变量的实现无关,需要在实现文件中实现访问方法。

方法族和属性的关系:

使用ARC的时候,必须注意方法的命名,不要和方法族发生冲突。

点操作符的使用方法:

OC2.0会在编译时把使用点操作符访问属性的过程理解为访问方法的调用,因为调用的是访问方法,所以无论对应的实例变量是否存在,只要访问方法存在,就都可以通过点操作符访问属性。

点操作符只能用于类类型的实例变量,不能对id类型的变量应用点操作符。因为没指定类型的情况下,编译器无法判断是否存在属性对应的访问方法。

复杂点操作符的使用方法:

连用点操作符:

当一个对象的实例变量是另外一个对象时,可用过连用点操作符来访问对象的实例变量中的成员。如果连用表达式中有一个是nil,则整个表达式的返回值就是nil。

连续赋值:

赋值时从右向左解释。

对递增,递减和复合赋值运算符的解释:

e = obj.depth++;

赋值表达式的右侧连续调用了getter和setter方法,相当于执行了[ obj setDepth: [ obj depth ] + 1 ]。最后为e赋值的是递增操作之前的depth的值。

self使用点操作符:

类的方法中可以通过对self应用点操作符来调用自己的访问方法。但要注意的是,不要在访问方法中使用self,否则会造成无限循环的递归,无法终止。

super使用点操作符:

可以通过给super加点操作符来调用父类中定义的setter和getter方法。例如:

- (void)setDepth:(int

)val {

super

.depth = (val <= maxDepth) ? val : maxDepth;

}

和构造体成员混用:

获取类属性的点操作符和访问结构体元素的点操作符可以混用。不能通过取地址符来对点操作符获得的属性取地址。

当给obj的实例变量contents发送消息时,你可能会这样写:[ obj.contents retain ]  。但要注意的是,实际上这行语句表示的是给getter方法的返回值发送了消息,并不一定会给obj的实例变量contents发送消息。

使用点操作符访问对象的实例变量和C语言中使用点操作符访问结构体的成员意义是不一样的。访问对象的实例变量的最正统的方法是通过 -> 操作符来访问。编译器在碰到点操作符的时候并没有直接访问实例变量而是调用了访问方法。

何时使用点操作符:

没有参数的方法,无论其是不是和属性相关,都可以通过点操作符来调用。但原则上还是只对属性声明中定义的属性应用点操纵符。

使用点操作符会带来调用方法的负担,影响性能。

严格来说,使用依赖于实现的方式来访问实例变量是不允许的,所以应避免直接访问实例变量。但属性对应的访问方法则一定要直接访问实例变量。

此外,在初始化方法中通过点操作符访问属性的时候要注意,因为初始化方法执行的时候这个实例还没完成初始化,属性对应的访问方法有可能还没生成。