第六条:理解“属性”这一概念
“属性”(property)是Objective-C的一项特性,用于封装对象中的数据。
Objective-C对象通常会把其所需的数据保存为各种实例变量。实例变量一般通过“存取方法”(accessmethod)来访问。
其中,“获取方法”(getter)用于读变量值。
而,“设置方法”(setter)用于写入变量值。
我们可以在类接口的public区段声明一些实例变量:
例:
@inteface person:NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
我们在Objective-C中很少这么做。
这种写法的问题是:对象布局在编译期(compile time)就已经固定了。只要碰到访问_firstName变量的代码,编译器就把其替换为“偏移量”(offset),这个偏移量是“硬编码”(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
这样做目前看来没有什么问题,但是如果又加了一个实例变量,那就麻烦了。
如果在firstName前加一个变量dateOfBirth,那么原来指向_firstName的地址偏移量就指向了_dateOfBirth了,就会读取到错误的值。
所以,如果代码使用了编译期计算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会出错。
例如:某个代码库中的代码使用了一份旧的类定义。如果和其相链接的代码使用了新的类定义,那么运行时就会出现不兼容现象。
Objective-C的做法是,把实例变量当做一种存储偏移量所用的“特殊变量”(special variable),交由“类对象”(class object)保管。偏移量会在运行期查找,如果类定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。甚至可以在运行期向类中新增实例变量,这就是稳固的“应用程序二进制接口”(Application Binary Interface,ABI)。
属性还有很多的优势:如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
如果你不想让编译器自动合成存取方法,则可以自己实现。如果你只实现了其中一个存取方法,那么另一个还是会由编译器来合成。还有一种办法能阻止编译器自动合成存取方法,就是使用@dynamic关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会报错,他相信这些方法能在运行期找到。
因为有些属性并不是实例变量,其数据来自后端数据库中。
属性的特质
属性可以拥有的特质分为四类:
原子性
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性。
如果属性使用nonatomic特质,则不适用同步锁。
读写权限
具备“readwrite”特质的的属性拥有“获取方法”和“设置方法”
具备“readonly”特质的属性拥有“获取方法”
内存管理语义
属性用于封装数据,而数据要有“具体的所有权语义”
assign:“设置方法”只会执行对“纯量类型”(scalar type,例如CGFloat或NSInterger)的简单赋值操作
strong:此特质表明了属性定义了一种“拥有关系”。为这种属性设置新值时,会先保留新值,并释放旧值,然后再将新值设置上去。
weak:此特质表明了属性定义了一种“非拥有关系”。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质类似assign,然而在属性所指的对象被销毁时,属性值也会清空。
copy:此特质所表达的所属关系与strong类似,然而设置方法并不保留新值,而是将其“copy”。当属性类型为NSString *时,经常用此特质来保护其封装性,保护数据不会在对象不知情的情况下被修改。
unsafe_unretained:此特质的语义和assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象被销毁时,属性值不会自动清空(“不安全”,unsafe),这一点与weak不同。
atomic与nonatomic的区别是什么呢?
具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性,这在多线程中,总会使用到有效的属性值。
但是,在iOS开发中,,你会发现,其中所有的属性都声明为nonatomic。这样做的历史原因是:在iOS中使用同步锁的开销较大,这回带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safe),若要实现线程安全的操作还要更深层次的锁机制才行。
例如:一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值,因此在iOS开发中,一般都会使用nonatomic特质。
要点:
1.可以用@property语法来定义对象中所封装的数据
2.通过“特质”来指定存储数据所需的正确语义
3.在设置属性所对应的实例变量时,一定要遵从该属性值所声明的语义
4.开发iOS程序时应该使用nonatomic属性,因为atomic会严重影响性能