一个对象的 property 能让其他对象检测或者改变该对象的状态。但是,一个设计良好的面向对象程序,应该避免直接让对象的内部状态被直接访问。相反,应该使用访问器方法(accessor methods, getters and setters),来作为一种抽象,跟对象的底层数据进行交互。看图:
@property指令的目的,是通过自动生成这些 accessor 方法,来让这些 property 的创建以及设置变得简单。它允许你在语义层面(semantic level)上来指定 public property 的行为,并且它会承担相关的实现细节。
@property指令中可以使用许多属性,来让你改变 getter 和 setter 的行为。有一些属性会决定 property 如何处理它们的底层内存,所以这篇文章也会同时介绍一些 Objective-C 内存管理。想要直到更多的关于内存管理的细节,可以阅读这里Memory Management。
####@property 指令
首先,让我们来瞧瞧当我们在使用**@property**指令的时候实际上在底层会发生什么事情。考虑如下例子,哟一个简单的 Car 类,还有它的相关实现。
1 | //Car.h |
1 | //Car.m |
编译器会为 running 属性生成相应的 getter 还有 setter 方法。默认的命名惯例是,使用属性本身作为 getter 方法,在它的前面加上前缀 set 作为 setter 方法,在它前面加上下划线 _ 则是它的实例变量,如下:
1 | - (BOOL)running { |
在使用**@property指令声明了属性之后,你就可以调用这些 setter getter 方法了,就好像他们在你的 .h 跟 .m 文件被编写进去了一样。你也可以在 Car.m 文件里面重写它们来提供自定义的 getter/setter 方法,但如果这样的话就必须要编写 @synthesize 指令的相关代码了(???)。然而,你已经很少情况需要自定义 accessor 方法,因为@property**能让你在一个抽象层面完成这些工作。
通过点运算符来访问的属性会根据所在的场景,被翻译成对应的 accessor 方法。所以下面的代码中的 honda.running 语句,在你向它分配一个值时实际上是在调用 setRunning:
,当你读取它的值时实际上是在调用 running
方法。
1 | //main.m |
要改变自动生成的 accessor 的行为,你可以在**@property**指令后面的圆括号指定一些属性。这篇文章剩下的部分将会介绍这些可用的属性。
####getter= 跟 setter=
如果你不喜欢**@property**默认的命名规范,你可以通过 getter=
还有 setter=
这两个属性来改变 getter 跟 setter 方法的名字。通常我们会在 Boolean 类型的 property 中使用,因为它们的 getter 按照惯例通常会有 is 作为前缀。例子如下:
1 | @property (getter=isRunning) BOOL running; |
现在,生成的 accessor 就会被命名为 isRunning 跟 setRunning 方法。要注意,property 的名字仍然为 running,并且这是你应该在点运算符后面使用的:
1 | Car *honda = [[Car alloc] init]; |
它们俩是唯一带参数的属性——accessor 方法的名字。剩下的其他属性都是 Boolean 标识。
####readonly
readonly 属性能让一个属性变成只读。它删除了 setter 方法并且禁止通过点运算符来对属性分配值,但是 getter 不会受影响。举个栗子,让我们像下面的代码那样修改 Car 类。注意如何通过逗号来分割指定的多个属性。
1 |
|
我们通过 startEngine 跟 stopEngine 方法的实现,来在类内部设置 running property,而不是让其他对象直接显式对它进行修改。相关的方法实现如下:
1 | // Car.m |
记住,**@property**同时也为我们生成一个实例变量,这就是为什么我们可以方位 _running 而不用在某处显式声明它。在上面的程序中,我们不能用 self.running
来对 _running 分配值,因为 running property 是 readonly 的。我们可以在 main.m 中添加如下代码片段来测试新的 Car 类。
1 | Car *honda = [[Car alloc] init] |
直到这里,property 对我们来说仅仅是让我们避免编写四班的 getter 跟 setter 方法。但是对于剩下的 attribute 来说就不是这样了,它们会明显地改变 property 的行为。同时,它们只能在 Objective-C 对象类型的 property 中使用,而在原始的 C 数据类型中则不可使用。
####nonatomic
**原子性(Atomicity)**跟 property 在一个线程环境中的行为有关。当你拥有不止一个线程,可能在某个时间点,setter 跟 getter 会被同时调用。这就意味着,getter/setter 能被另外一个 setter/getter 操作打断,而因此导致数据冲突。
atomic property 会将底层对象锁住来阻止这种情况发生,保证了 get 或者 set 操作工作时对象的值是完整的。然而,重要的是,这只是线程安全的一个方面,并不是说使用 atomic property 就意味着你的代码一定是线程安全的。
使用**@property**声明的property默认具有 atomic 属性,并且这会导致一些开销。所以,如果你并不是在多线程环境(或者你正在实现自己的线程安全),你可以用 nonatomic 属性来重写这个行为,如下:
1 | @property (nonatomic) NSString *model; |
这里有一些关于 atomic property 的小小警告。Atomic property 的 accessor 方法(getter和setter)需要成对地被自动生成或者用户自己定义。换句话说,你要么不定义,要定义就将 setter 跟 getter 一起定义。只有 non-atomic property 才能让你混搭 synthesized accessor 跟自定义的 accessor 方法。想清楚地了解,你可以将上面代码里面的 nonatomic 移除,然后再 Car.m 里面添加自定义的 getter 方法。
####内存管理
在所有面向对象编程(OOP)语言中,对象存在于计算机的内存中,而内存是稀缺资源,在移动设备中尤为这样。内存管理系统的目的是,以一种有效率的方法,来确保程序不会占有的内存空间不会比他们实际所需要的更多。
很多语言通过垃圾回收机制来实现内存管理,但是 Objective-C 使用一种更加有效率的替代方法,称为 对象拥有关系(object ownership)。当你开始与一个对象进行交互,你会被认为拥有(own)该对象,这意味着只要你在使用着它,它就会一直存在。当你完成操作之后,你将释放对该对象的拥有关系(ownship)。而在释放了之后,如果该对象没有其他 owner,操作系统将会销毁该对象,并且释放底层的内存空间。
而在 Automatic Reference Counting 出现之后,编译器将自动管理你的所有对象 ownership 。这就意味着,在大多数情况下,你将不再需要担忧系统实际上如何进行内存管理。但是,你应该了解 @property 里面的 strong,weak 和 copy 这几个属性,因为他们会告诉编译器相应的对象之间会拥有怎样的关系。
#####strong
strong 属性会对被分配到 property 的对象创建拥有关系(owning relationship)。这是所有对象类型的 property 的默认属性,同时也是安全的默认属性,因为它确保了被分配给 property 的对象不会意外被释放。
下面通过一个新的 Person 类来观察它是怎样使用的。Person 类仅生命了一个 name property:
1 | // Person.h |
它的实现如下。Person 类使用**@property**生成的默认 accesor 方法。它同时重写了 NSObject 的 description 方法,该方法将返回一个代表该对象的字符串。
1 | // Person.m |
下一步,让我们在 Car 类中添加一个 Person 类型的 property。将 Car.h 修改如下:
1 | // Car.h |
然后,考虑在 main.m 中的如下代码:
1 | // main.m |
由于 driver 是强关系(strong relationship),对象 honda 拥有 john 的 ownership ,这保证了只要 honda 需要它,就不会被释放掉。
#####weak
strong 属性是 object property 中使用得最普遍的。然而 strong reference 会引来一个问题。例如,当我们需要引用 driver 正在驾驶的 Car 对象。首先,让我们在 Person.h 中增加一个 Car 类型 property:
1 | // Person.h |
@class Car
代码行是对 Car 类的超前声明(forward declaration)。它像是在告诉编译器:“相信我,Car 类是存在的,所以现在先不要试图马上去寻找它”。我们使用这行代码,而不是使用我们习惯的 #import
语句,是因为 Car 同样也 import Person.h,否则将会导致一个无穷无尽的 import 循环。(编译器不喜欢 endless loop)
下一步,在 honda.driver 赋值语句后面加入下面代码:
1 | honda.driver = john; |
我们现在有从 honda 指向 john 的关系,同时也有从 john 到 honda 的关系。这就意味着双方彼此拥有,因此内存管理系统永远不能释放他们,即使他们不再被需要。
在 Car 跟 Person 类之间的 retain cycle
这被称为 retain cycle ,是一种内存泄露,如我们所知内存泄露是多么的糟糕。幸运的是,要解决这个问题非常简单,只需要让其中一个 property 对其对象的引用更改为 weak
reference。在 Person.h,如下更改 car property 的声明。
1
@property (nonatomic, weak) Car *car;
weak 属性创建了一种对 car 的“非拥有关系(non-owning relationship)”。这允许 john 拥有指向 honda 的关系,同时避免了 retain cycle。但是,这也同时可能会导致这样的一种情况:john 仍然持有指向 honda 的引用,但是 honda 已经被销毁了。如果这种情况发生了, weak 属性会贴心地将 car 设置为 nil,来避免悬空指针(dangling pointer)出现。
从 Person 类指向 Car 类的 weak reference
一种普遍的使用 weak 属性的场景是在父子关系的数据结构中。一般情况下,父对象应该持有指向孩子的 strong reference,而子对象应该持有指向父对象的 weak reference。weak reference 同样也是在 delegate 设计模式中固有的一部分。
值得再次提醒,两个对象之间不应该彼此持有指向对方的 strong reference。weak 属性让我们得以维持一个循环的指向关系但是不会引入 retain cycle。
#####copy
copy 属性是 strong 的另一种替代方式。copy 会对分配给 property 的对象创建一个拷贝,然后持有该拷贝的 ownership,而不是持有原对象的 ownership。只有遵从了 NSCopying 协议的对象能使用它。
代表值的 property(而不是连接或者引用)可以使用 copy 属性。例如,开发者通常拷贝 NSString property 而不是持有它们的 strong reference:
1 | // Car.h |
现在,Car 会持有我们分配给 model 的对象的一个新拷贝的 strong reference。如果你 working with mutable values,这样做有一个好处:copy 让你得以“冷藏”该可变值,让 property 持有被赋值时的值,而后面原可变值如何改变都不再影响此 property。例子如下:
1 | // main.m |
NSMutableString 是 NSString 的子类,它可被修改。如果 model 属性没有创建对原对象的拷贝,我们将会看到 NSLog 打印出来的内容为“Nissa Versa”。
####Other Attributes
上面提到的 @property 的 attribute 是你现在设计 Objective-C 应用所需要的(iOS5+),但还有一些其他的 attribute,你可能会在一些比较旧的库或者文档中遇到。
#####retain
retain 属性是 MRR(Manual Retain Release) 版本的 strong,它会产生相同的作用:获得被分配的值的 ownership。在 ARC 环境下你不应该使用这个值。
#####unsafe_unretained
使用 unsafe_unretained 属性的 property 跟使用 weak 属性的 property 类似,但是它在引用的对象被销毁的时候并不会自动将 property 设置为 nil。你唯一一个使用 unsafe_unretained 的理由应该是——你在不支持 weak 属性的环境中编码。
#####assign
assign 属性在 property 被赋予一个新的值时,不会执行任何内存管理的调用。这是原始数据类型 property 的默认属性。在 iOS5 之前,它也被用来作为 weak reference 的一种实现方式。像 retain 一样,你不应该在 ARC 环境中使用它。用于OC对象时,assign
跟 weak
类似,但是在所指向的对象被销毁之后,不会将 property 清空。
####总结
Attribute | Description |
---|---|
getter= | 为 getter 方法取一个自定义的名字 |
setter= | 为 setter 方法取一个自定义的名字 |
readonly | 编译器不会为你生成 setter 方法 |
nonatomic | 不保证在多线程环境中,对属性的访问的完整性。但它比默认的 atomic 更加高效 |
strong | 在 property 与被分配的值之间创建拥有关系。它是 object property 的默认属性 |
weak | 在 property 与被分配的值之间创建非拥有关系。用它来避免 retain cycle |
copy | 创建被分配的值的拷贝,而不是直接引用原对象 |
以上