Objective 属性的属性properties' attribute
前面翻译过Rypress写的关于Objective-C属性properties的文章,文章只翻译了一半,这次继续。
上次提到@properties可以指定类属性的getter和setter。我们可以通过设定gettter、setter在实现文件中通过指定的写方法实现setter、getter。
其实,properties的属性不仅仅是有setter、getter。除此之外,我们还会见到以下的一些属性attributes:
1 | atomic, nonatomic, readonly, readwrite, assign, retain, copy, getter, setter |
下面,我们将重点看一下properties的attributes。
readyonly属性
只读属性可以很方便的将properties设置成为只读。这货将setter方法禁用了,并且对点操作符(.)做了保护(不能通过.操作符来设定属性的值),但是getter方法不受影响。举个例子(注意,当属性properties有多个attribute时,中间的分隔符是英文状态的 ,
)
1 | // 文件Car.h |
这里我们定义了两个方法来操控running的取值startEngine、stopEngine。接下来,我们来实现它们:
1 | // 文件Car.m |
需要记住的是,@properties会自动给我们生成一个实例变量,这样下来我们就可以通过下划线的方式来访问属性了(Notice
不要尝试使用self.running这种形式的动作来访问了,因为running现在是readonly的呢)。
接下来,我们就开一下我们的新车吧!
1 | // Codes in main.m |
到此为止,我们了解了属性的一些便捷操作,我们知道了这家伙可以自动为我们创建类似于setter和getter的方法。然并卵,它却不是属性properties的唯一属性attribute!并且这样定义的setter和getter只适用于对象类型的数据,在面对C的数据结构的时候,它就不那么好使了!
原子性
原子性(atomicity)是定义属性properties在线程当中的表现形式。当你有多于一个的线程的时候,程序可能会在同一时间调用了setter或者getter方法。也就是说,我们的setter和getter可能会被其它线程破环掉,如此一来,我们的程序很可能会驾崩~~~
原子性可以给我们的属性properties上锁,上锁之后的属性就可以不被其它线程破坏,从而使我们可以畅快的存取属性值!可惜,蛋疼的是,使用了原子性的属性并非是觉得的线程安全的!我们在敲代码的时候还得兼顾其它细节!
默认情况下,属性properties就是atomic的,即原子性的。这样下来,如果你的程序并非多线程的,或者你自有一套线程安全管理机制,那么你就需要重新定义属性的原子性了!你的代码可能看起来像下面这样:
1 | @property (nonatomic) NSString *model; |
需要注意的一个细节是:原子的访问器必须存在(它可以是用户定义,也可以是代码生成)。只有非原子性的属性才可以让你自定义合成访问器!你可以通过移除上面代码当中的nonatomic之后添加自定义的getter方法。
内存管理
在任何面向对象的编程语言当中,我们总是将对象存储在内存中。我们知道对于设备,内存是个很稀缺的玩意儿,尤其在移动设备中,内存显得异常珍贵!所以,一个好的内存管理机制,很有必要!在很多语言当中,对于内存的管理有一套垃圾回收机制,但是Objective-C却是一套谁创建谁销毁的机制!这样一来,当你申请了内存之后,你就拥有了那块内存,直到你销毁它之前,它就一直属于你!并且,在你完成工作之后,如果不再使用该内存,并且没有其它的成员使用该内存,你就需要手动释放它!
所幸,Objective-C有一套自动引用计数技术,编译器会管理你对象的所有权。大多数情况下,你是无需担心内存管理的实际工作情况!但是,你需要明白的是,strong, weak, copy这三个attribute会告知编辑器使用什么方式来管理内存。
strong属性
当我们把属性变量声明称为strong时,你就有了这个变量的控制权!如果有多方访问这个变量,那么只有它们全部不再操作时,这个变量才会销毁(release)。这样一来,线程就是安全的了!
接下来我们看看strong是如何工作的!
1 | // Person.h |
实现文件如下。代码中我们使用了默认由property生成的访问器。代码重写了NSObject的description方法。
1 | // Person.m |
接下来,我们在Car类中定义一个Person属性。新的Car.h代码如下:
1 | // Car.h |
在Person.m文件中实现它
1 | #import "Foundation/Foundation.h"; |
因为我们在声明属性Person的时候指定了strong,所以httpbin可以在car中使用。
weak属性
大多数情况下strong属性能解决我们的问题!但是有些情况是strong却存在问题。比如,我们现在有个需求是在Person类中知晓Car的信息。代码如下:
1 | // Person.h |
@class Car这一行表示在类中声明了Car类。意思就是高速编译器,我需要引用Car类,并且Car类存在。我们之所以不使用import表达式,是因为Car类中已经存在了Person类的import。如果我们继续采用import方式,编译器会出问题!
接下来,在main.m中增加如下代码。
1 | bmw.driver = httpbin; |
这样下来我们就有了如图的对象引用关系,如此,我们就形成了你中有我,我中有你的局面!好吧,这样下去,内存管理系统就无法销毁任何一个不再使用的对象了!
这种方式叫做循环引用(retain cycle),这样的下场是导致内存泄漏!要解决这个问题也很方便!将被任何一方被引用的属性设置成为weak即可!我们修改Person.h中的代码如下:
@property (nonatomic, weak) Car *car;
weak属性定义的属性可以防止死循环。但是这样下来,即便bmw销毁httpbin还是会存在引用。还在weak属性会将car设置成为nil来防止空指针!
我们可以采用图示如下:
weak属性在父子关系结构的数据中比较常见。约定为,在父类中定义个strong的属性供子类引用,而在子类中维护一个weak属性来调用父类属性。在代理模式当中,weak引用也是很要的一环。
内存释放的前提是,两个对象中没有任何一个引用存在!而weak的存在则可以让对象周期性的存在,而不去创建一套周期的管理机制。
copy属性
copy是strong的替代属性。但是它采用的是不一样的内存管理机制!即,它直接将属性拷贝了一份!只有符合NSCopying协议(protocol)才可以使用这个属性。
值类型的属性比较适合复制!例如,我们需要将NSString强制引用!
1 | @property (nonatomic, copy) NSString *model |
这样,我们就可以给car储存一个新的饿model实例。当我们使用mutable类型的值的时候,我们就可以将分配的值冻结。再来一份代码:
1 | #import "Foundation/Foundation.h"; |
NSMutableString是NSString的子类,如果model没有创建一份原始的实例,那么我们可以看到它被更改了。