Objective-C的开发者们都知道,OC中的属性(Property)通常都有一组特性(Attributes)来说明该属性的一些附加信息。在Swift当中,这个特性的功能“似乎”是被取消掉了,但是,我们仍然可以通过一些不同的方法来指明属性的这些特性。
基本的属性声明
使用属性,我们可以避免手工编写繁琐的setter和getter方法,避免因为这些方法来内存的问题,同时也节省编写代码的时间。
在Objecitve-C中,我们声明属性一般都是这样声明的:
// Some.h
@property int count;
// Some.m
@synthesize count;
而在Swift当中,我们则是这样声明就可以了:
// Some.swift
var count: Int
注意的是,Swift中的属性只能够声明在类的上下文环境当中,而不能声明在其他地方(包括类当中的方法),否则Xcode就不会认为它是属性,而是认为它是一个局部变量了。
读写特性
特写特性主要是对属性的读写权限进行控制与操作的,这个特性是针对于外部的。因为属性实际上会生成两个方法:setter
和getter
,外部访问这个属性实际上是调用这两个方法来对属性进行操作的。
在Objective-C中,拥有这样两个读写特性:readwrite
和readonly
,意思简单明了,就是可读可写
以及只读
。默认情况下,属性默认是可读可写
的。
比如说我们在Objecitve-C中可以声明这样一个只读属性:
@property(readonly) int count;
这样这个属性在外部只能够读取(使用getter),而不能够修改了,因为Xcode不会生成这个属性对应的setter
方法。
而在Swift当中,则有如下两种选择:
let count: Int
注意:在Swift1.2之后,常量可以事先不声明值,而是事后声明,但是常量的值一经确定,那么常量仍然就不可改变。
而对于计算属性来说,只读
特性的属性只需要提供getter
方法就可以了。而可读可写
特性的则必须要提供getter
和setter
方法。例如:
let count: Int {
get {
// ...do something
}
}
setter语意特性
setter语意特性主要是用来告诉Xcode,对于这个属性,应该如何去自动实现它的setter
方法。这个特性主要是针对非ARC情况的。
在Objective-C中,拥有三个setter语意特性:assign
、retain
和copy
,默认情况下属性特性是assign
的。
-
assign
,简单赋值特性,它不会对索引计数(Reference Counting)进行更改。 -
retain
,释放(release)旧的对象,然后将旧对象的值赋予输入对象,再将输入对象的索引计数增加1(retain)。 -
copy
,建立一个索引计数为1的对象,然后释放掉旧对象。
是不是不好理解?没关系,我们举一个例子来研究下。
比如说这样一个语句声明:
NSString *paper = [[NSString alloc] initWithString:@"纸"];
这一段代码将会执行以下两种动作:
-
在堆上分配一块内存空间,用来存储
@"纸"这个字符串对象,我们假设这块内存地址为
0x1111`。 -
在栈上分配一块内存空间,用来存储
paper
这个对象,我们假设这块内存地址为0x2222
。
对于setter语意特性来说,它们都是在执行setter
方法后,对“旧对象”执行这几个操作而已,这三个操作实际上也是Objective-C内存管理机制的重要组成部分。
assign
那么对于assign
来说,声明一个带有assign特性的属性,就相当于如下语句:
NSString *newsPaper = [paper assign];
此时,paper
和newspaper
的内容都是指向地址0x1111
的纸
。也就是说,newspaper
只是paper
的一个别名,对newspaper
进行变更,也会对paper
进行变更,因为它们本质上都是一个东西。因此,他们的引用计数并不会增加。
换句话说,assign
就是相当于是给一个保险柜(存放纸
的存储空间)只配了一把钥匙(指针),无论是谁来要去打开保险柜,实际上都是使用了这把钥匙。
Retain
那么对于retain
来说,声明一个带有retain特性的属性,就相当于如下语句:
NSString *newsPaper = [paper retain];
这个时候,newsPaper
的地址就不是0x2222
了,而是变成了一个新的地址,只不过它的内容仍然还是位于地址0x1111
的纸
而已,而此时引用计数就会增加1。
换句话说,retain
就是相当于给保险柜配了多把钥匙,这些钥匙都能够打开这个保险柜,每多配一把钥匙,那么引用计数(保险柜所拥有的钥匙数量)就要增加1。
Copy
那么对于copy
来说,声明一个带有copy特性的属性,就相当于如下语句:
NSString *newsPaper = [paper copy];
这个时候,就会在堆上重新开辟一段内存空间,来存放纸
这个对象,同时也会为newsPaper
也分配一段新的内存空间。这个时候,newspaper
的地址就是新的了,而它里面的内容也会是新的了。
换句话说,copy
就是相当于重新搞了个保险柜,可能保险柜里面的东西都是一样的,但是钥匙却变成两把了。不过每个保险柜所对应的钥匙数量仍然都是为1。
什么时候使用这些语意特性呢?
只要是值类型、简单类型的类型,比如说NSInteger
、CGPoint
、CGFloat
,以及C数据类型int
、float
、double
等,都应该使用assign
。
那么对于含有可深复制子类的对象,比如说NSArray
、NSSet
、NSDictionary
、NSData
、NSString
等等,都应该使用copy
特性。
注意:对于
NSMutableArray
之类的可变类型,不能够使用Copy特性,否则初始化会出现错误。
至于其他的NSObject
对象,那么都应该使用retain
来进行操作,这也是绝大多数所使用的情况。
等等,Swift呢?
我们开头已经提到过,getter语意特性是针对非ARC情况的,我们都知道,Swift语言是直接采用ARC进行内存管理的,所以这些操作在Swift中都是找不到对应的情况的。
不过,对于Swift来说,有一项特性和copy
特性是十分相似的。
NSCopying
Swift中用@NSCopying
特性来修饰存储属性,这个特性将使该属性的setter与属性值的一个副本拷贝合成,也就是说,@NSCopying
特性也是将属性进行了复制,开辟了一段新的内存空间,从而达成“两个保险箱”的作用效果。
和copy
特性不同的是,这个特性将会导致属性的getter
方法是用copyWithZone
方法所返回的值,而不是返回属性本身的值。因此,这个属性的类型必须要遵循NSCopying
协议。
所有者特性
对于ARC来说,上一节中所说的getter语意特性
将被所有者特性
所代替。
在Objective-C中,拥有两个所有者特性:strong
和weak
。默认情况下属性特性是strong
的。
对于strong
来说,它就相当于getter语意特性
中的retain
特性,即这个特性的属性将会成为对象的持有者。这个特性称之为强引用
。
对于weak
来说,它声明的属性不会拥有这个对象的所有权,当对象被废弃之后,对象将被自动赋值为nil。
那么怎么来理解strong
和weak
呢?我们仍然以之前的保险柜的例子:
假设保险柜现在是超市外面的寄存处的保险柜,大家都知道,寄存处在某个保险柜不再使用的时候(对象被释放),就会回收这个保险柜(回收内存空间),以供下一个人使用。
我们申请了一个保险柜(申请内存空间)之后,我们将我们的东西(对象)存放到保险柜当中,那么如何要保证我们的东西不会被超市坑掉呢?这就需要保险柜给我们的开门的钥匙(强引用),只要这个钥匙还在,那么我们的东西就不会被回收(一定期限内)。
再假设为了防止恐怖分子,超市对这些保险柜都置放了扫描装置,只要我们的东西还在保险柜里面,那么保安就能够通过装置看到我们的东西(弱引用)。而如果我们用钥匙把里面的东西拿走了,将钥匙归还了(销毁对象)。那么保安就不能看到我们的东西了。为了节省电力,这个保险柜的扫描装置就会进入休眠(所有弱引用变为nil)。
面对ARC机制中,最令人头疼的就是“循环强引用”的问题,所谓循环强引用,就是我们申请了两个保险柜,然后分别将另外一个保险柜的钥匙锁在了保险柜当中。这样就会造成什么现象呢?我们完全就无法归还钥匙了,这两个保险柜就无法再重新使用了。那么使用弱引用,就不会出现这个问题了。
好了,我们就此打住,关于循环强引用的解决方案,不在本文的叙述范围之中。
那么Swift呢?和Objective-C一样,Swift同样也有strong
和weak
两种所有者特性,但是,Swift还有另外一种特性:unowned
,无主引用。无主引用和弱引用的作用基本是一样的,不过与弱引用不同的是,无主引用不能够为nil。
在一般的开发流程中,往往都建议将delegate
和IBOutlet
设置为weak
特性,因为这两个属性都极有可能会被其他类所拥有,设置为weak特性可以防止循环强引用的产生。
原子特性
原子特性,简要来说,是针对多线程而设置的。Objective-C拥有两种原子特性,分别是atomic
和nonatomic
。
我们知道,如果使用多线程的话,有时会出现两个线程互相等待而导致的死锁现象。使用atomic
特性,Objective-C可以防止这种线程互斥的情况发生,但是会造成一定的资源消耗。这个特性是默认的。
而如果使用nonatomic
,就不会有这种阻止死锁的功能,但是如果我们确定不使用多线程的话,那么使用这个特性可以极大地改善应用性能。
相比之下,swift目前还不支持这些特性。如果我们要实现线程安全,似乎只能使用objc_sync_enter
此类的方法,来保证属性的处理只有一个线程在进行。或者使用属性观察器来完成这些操作。
总结
我们总共介绍了四种属性特性,分别是读写特性、setter语意特性、所有者特性和原子特性。一个特性中,只能够有一个出现,不能够出现多个读写特性的情况。此外,setter语意特性和所有者特性也是互斥的,因为一旦使用了所有者特性,就说明项目使用了ARC,而ARC是不支持setter语意特性的。
对于Swift来说,我们目前能够以其他方式实现的,也就是“读写特性”和“所有者特性”而已,其他的特性目前是赞不支持的。因此,可以看到,在不久的将来,原子特性可能也会提供支持。
综上所述,我们分析和对比了Objective-C和Swift的属性特性,可以看出Swift使用了一些特殊的特性来实现原有的Objective-C属性特性的功能,虽然目前还有很多欠缺的地方,但是也不失减轻了开发者的负担。