文章目录
  1. 1. 基本的属性声明
  2. 2. 读写特性
  3. 3. setter语意特性
  4. 4. assign
  5. 5. Retain
  6. 6. Copy
  7. 7. 什么时候使用这些语意特性呢?
  8. 8. 等等,Swift呢?
  9. 9. NSCopying
  10. 10. 所有者特性
  11. 11. 原子特性
  12. 12. 总结
作者:星夜暮晨 
地址:http://www.jianshu.com/p/9a67ffb9eb6a

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属性特性的功能,虽然目前还有很多欠缺的地方,但是也不失减轻了开发者的负担。


本文出处程序员头条:http://www.iswifting.com/2015/12/19/swift-object-c-property/
转载请在开头注明本文出处。

欢迎关注本站微信公众号:为程序员提供最优质的博文、最精彩的讨论、最实用的开发资源;提供最新最全的编程学习资料:PHP、Objective-C、Java、Swift、C/C++函数库、.NET Framework类库、J2SE API等等.并不定期奉送各种福利.
微信公众号猿圈:CodePush

文章目录
  1. 1. 基本的属性声明
  2. 2. 读写特性
  3. 3. setter语意特性
  4. 4. assign
  5. 5. Retain
  6. 6. Copy
  7. 7. 什么时候使用这些语意特性呢?
  8. 8. 等等,Swift呢?
  9. 9. NSCopying
  10. 10. 所有者特性
  11. 11. 原子特性
  12. 12. 总结