第7章 属性声明
OC 2.0支持使用简洁代码来调用访问方法,也允许自动生成访问方法。通过这些方便的功能,即使不手动实现访问方法,也能够操作对象的属性值。
7.1 属性是什么
7.1.1 使用属性编程
对象的实例变量一般被称为属性,状态。
属性声明是一种声明变量为属性的语法,同时引入了更简单的访问属性的方法。
对象的属性值都保存在实例变量中,可以直接访问或通过方法访问。也就是说,声明了实例变量或定义了访问方法就相当于定义了属性。
随着属性声明的引入,OC的编程风格也有了很大的变化
自动生成访问方法,能够为指定的实例变量自动生成访问方法。生成getter和setter方法。
自动生成实例变量
如果不存在同名的实例变量,在生成访问方法的同时,也会生成实例变量。
更简单地调用访问方法
可以通过点操作符(.)来调用访问方法,无论是setter方法还是getter方法都可以通过点操作符来调用。
同时点操作符也不仅限于通过属性声明生成的getter/setter方法,只要定义了访问方法(包括手动),就可以使用点操作符调用。总结起来就是访问方法可以被点操作符调用。
属性的内省
通过内省可以动态查询类中声明的属性以及属性的名称和类型
7.1.2 属性的概念
从KVC(key-value coding)之后,属性就被赋予了“从外部可以访问的对象的属性”。
OC2.0 引入了属性声明和点操作符来调用访问方法的手法。使用属性声明可以更简洁的实现访问方法。另一方面,不仅仅是访问方法,KVC中所有定义的实例变量都可以被当做属性处理。相比较而言,KVC的属性是一种更广泛的概念。
内省:说明数据的形式和内容的数据叫做元数据(meta data),说明信息的信息被叫做元信息(mate information)。例如,一个文件的原数据是:文件名为:会议记录“;生成时间为昨天16:15;文件格式为:HTML;文件编码为Unicode;只读。
一个类包含的方法和属性的相关信息也可以被看成为元信息。在面向对象的语言中,通过程序动态访问这些元信息的功能叫做内省或者反射,访问某个对象是否含有某种功能就叫做元方法。
OC能够在运行时取出类和协议的相关信息,而OC2.0能够在运行时取出类属性的定义,这是OC2.0新增加的功能。
7.2 属性的声明和功能
第4张说明了访问类实例变量中增加访问方法的重要性,为了封装,可并不是所有实例变量都需要增加访问方法,OC2.0中新增加的属性声明的功能,这个功能可以让编译器自动声明与实例变量同名的方法,省去对实例变量方法的getter/setter方法的定义。
#import
@interface Creature:NSObject{
NSString *name;
int hitpoint;
int magicpoint;
}
- (id)initWithName:(NSString *)str;
- (NSString *)name;
- (int)hitpoint;
- (void)sethitpoint;
- (int)level;
@end//
@property int hitpoint ;
属性声明功能等于直接声明了getter和setter二个访问方法、
属性声明时还可以设置属性的自定义选项,只想声明只读的访问方法(readonly)。
@property 和是否声明了实例变量无关。level 也可以用 @property 实现
7.2.2 属性的实现
@synthesize hitPoint
通过@synthesize 编译器功能会让编译器自动为类的实例变量生成自动访问方法。
属性声明@property加上@synthesize 完成了属性的声明和实现、
@synthesize也可以为readonly生成访问方法getter。
这种情况下,属性名和类型一定要和声明一致,比如在level中没有为level属性进行@synthesize。而是手动定义了getter方法。
我们可以通过@dynamic告诉编译器自动合成无效,用户会自己手写getter和setter方法。
有时候你需要让方法和实例变量的名称不同,这时候需要将不同名的方法绑定到实例变量上。
@synthesize value = runningAverage
如上语句生成了vaule的访问方法,并将它绑定到了实例变量runningAverage上
我们可以在类的实现部分中声明一部分实例变量或全部实例变量,和第4章一样。
7.2.4 通过@synthesize生成实例变量
属性声明的变量在接口文件和实现文件都没声明的情况下,通过使用@syntehesize,就可以完成在类的实现文件中生成同名同类的实例变量,缩小了任务。
7.2.5 给属性指定选项
用@property 声明的时候可以给属性指定readonly选项,而除了readonly之外,还有一些其他选项,也可以给一个变量指定多个选项,选项之间逗号隔开。
指定方法名 getter = getter方法名 setter = setter方法名 显示指定getter方法和setter方法的名字。
读写属性 readonly ,readwrite 只读,只写。
赋值时的选项 assign 单纯赋值 retain 进行保持操作
unsafe_unretained 同assign一样(ARC下的操作)
strong (同retain一样 ,ARC下)
weak 弱引用(用于ARC)
copy(复制对象)
原子性操作 nonatomic (非原子性操作。非线程安全)
也可以不使用的默认访问方法名,而通过setter option来指定访问属性用的方法名。例如,我们可以通过下面这行语句来指定实例变量hitpoint和hitpoint的setter方法为setValue:,
@property (setter = setValue:) int hitpoint;
7.2.6 赋值时的选项
我们可以为可读写的@property 设置选项,共有6种选项:assign , retain, unsafe_unretained,strong,weak.copy. 选项之间是排他关系,只能设置其他中的一种或者不设置,根据所修饰的属性是否为对象类型或者采取内存管理方式的不同,选项的意义也会发生变化
unsafe_unretained 和 strong 主要被使用在ARC的情况下,分别和assign 和 retain 具备一样的功能
总结
(1)@property的属性不是对象类型
不是对象类型的属性只可以单纯赋值,因此不需要指定任何选项,或者也可以指定assign选项,通过使用@synthesize,能够生成getter和setter方法。
- (TYPE)name{ - (void)setName:(TYPE)obj{
return name; name = obj;
} a } b
(2)@property的属性是对象类型,且手动管理内存
不指定任何选项的情况下,编译的时候会告警。指定了assign选项(赋值)的情况下,通过@synthesize会生成和(1)一样的getter、setter方法
指定了retain(保持)选项,会生成 c 的setter方法,在赋值时应该对该对象进行保持操作
- (void) setName:(Type)obj{
if(name != obj){
[name release];
name = [obj retain];
}
} C
指定了copy选项的情况下,会生成d的setter方法,并使用对象的一个副本来进行赋值,就是不使用输入的对象对属性进行赋值,而是生成一个副本进行属性赋值,这种赋值方式只使用于对象类型,并且要求该对象遵循NSCopying协议,且能够使用copy方法。
- (void)setName:(TYPE)obj{
if(name != obj){
[name realse];垃圾回收的情况下没有意义
name = [obj copy]
}
} D
(4) 属性是对象类型,且使用垃圾回收管理内存
这种情况下,如果不指定任何选项或指定了assign选项,@synthesize会生成a,b的getter,setter方法。
但要注意,只有符合NSCopying协议可以利用copy方法的类实例变量,如果不指定任何选项,则会告警。
选项retain 和 weak 没有意义,指定了也会被忽略,并且执行的也是assign选项,实例变量则需要用_ _ weak选项修饰。
@property (assign) _ _ weak NSString *nickname;
指定copy后,会生成 d中的setter方法、
7.2.7 原子性
nonatomic这个选项是在多线程环境下使用的,nonatomic表示访问方式是非原子的,原子性是多线程的一个概念,如果说访问方法是原子的,那么就意味着多线程环境下访问属性是安全的,在执行的过程中不可能被打断。缺省的情况下访问方法是原子的。
c和d都是指定了nonatomic选项的实现。当属性为对象类型时,使用了retain并且没有指定nonatomic时,getter和setter的实现如下所示,没指定nonatomic,访问方法中需要使用lock和unlock来保证方法的原子性。
- (TYPE)name { - (void)setName:(TYPE)obj{
[_ex lock]; // 局部锁 [_ex lock];//局部锁
TYPE rth = [[name retain]autorelease]; if(name != obj){
[_ex unlock]; [ name release];
return rtn; name = [obj retain];
} }
[_ex unlock] ;
} b
还有一些没说
7.2.8 属性声明和继承
子类可以使用父类中定义的属性,也可以重写父类中定义的访问方法。但是,父类中属性声明时指定的各种属性(assign,retain),或者为实例变量指定的getter和setter的名称等必须完全一样。
唯一一个特别的情况是,对于父类中被定义为readonly类型的属性,子类中可以将其变为readwrite。虽然不可以在子类中使用@synthesize 对父类中的实例变量生成访问方法,但可以手动实现对应方法,这是为了防止子类轻易访问父类中隐藏的实例变量。
属性的声明可能会包含范畴或协议。这种情况下实现文件中不可以使用@synthesize,原理是范畴和协议都和实例变量的实现无关,需要在实现文件中实现访问方法。
7.2.9 方法族和属性的关系
使用ARC的时候,必须注意方法的命名,不要和方法族发生冲突。
属性声明的时候回默认生成和属性同名的getter访问方法,需要注意属性名是否和方法名冲突,特别是new开头的属性名。
7.3 通过点操作符访问属性
OC 2.0 中新增了使用点操作符来访问属性的功能
上面说过在key-value coding 中属性被赋予了从外部可以访问的对象属性
访问操作符是点操作符、
OC 2.0 会在编译时把使用点操作符访问属性的过程理解为访问方法的调用,因为调用的是访问方法,所以无论对应的实例变量是否存在,只要访问方法存在,就可以通过点操作符访问属性。
点操作符只能用于类类型的实例变量,不能对id类型的变量应用点操作符。在动态类型下,编译器无法判断是否存在属性对应的访问方法。
操作 使用点操作符 使用消息表达式
赋值(setter) obj.name = val; [obj setName:val];
获取(getter) val = obj.name; val = [obj name];
7.3.2 复杂的点操作符的使用方法
(1)连用点操作符
n = obj.productList.length;
obj.contents.enable = YES;
点操作符按照从左向右的顺序进行解释,所以上面的表达式可被解释为
n = [ [obj productList] length];
[[obj.contents] setEnabled:YES];
(2)连续赋值
n = 0;
k = obj.count = obj.depth = ++n;
k = (obj.count = (obj.depth = ++n))
点操作符与C语言点宏定义不同,不会对n重复+1;
7.3.3 何时使用点操作符
没有参数的方法,无论他是否与属性相关,都可以使用点操作符来调用,其机制和getter方法是一致的
OC中使用点操作符会带来调用访问方法的负担,所以在严格要求性能的条件下,使用点操作符并不是一个很好的选择。
在其他语言中,通过点操作符调用对象的方法。但对OC来说,使用点操作符的目的只是访问属性。