关于引用类型与值类型,有过其他平台开发经验的朋友相信不会陌生。我们使用其他开发环境的大多数情况下,我们其实都在于引用类型打交道,了解值类型和引用类型的使用对我们写出更加可靠的程序非常的关键,那么我们来看一下在 Swift 又是如何使用引用类型与值类型的呢。
引用类型和值类型的区别
- 引用类型
首先,咱们先了解一下什么是引用类型和值类型。
如果之前使用过 Objective-C 进行过开发的话,你应该接触了很多引用类型的变量。
在 Swift 中,所有使用 class
关键字定义的类都是引用类型,比如:
class Book { var name:String ... } |
我们在使用引用类型的时候,会遇到这样的情况:
var book1 = Book() book1.name = "swift" var book2 = book1 book2.name = "star" print(book1) // star |
我们修改 book2 变量的 name 属性的时候,同时 book1 的属性也发生了更改。这个就是引用类型的特性。我们对 class 类型的对象进行相互赋值操作的时候,实际上并没有复制对象的值,而是复制了对象的地址,也可以说是引用。
复制引用的一个好处是,它能够提升程序的效率。相比于赋值对象的值,直接赋值它的地址的代价就小得多。
- 值类型
而值类型的行为,就和引用类型有所不同了。在 Swift 中,定义值类型的方式是使用 struct
关键字:
struct Book { var name:String ... } |
对于值类型的赋值操作,它的行为是这样的:
var book1 = Book() book1.name = "swift" var book2 = book1 book2.name = "star" print(book1.name) // swift |
同样的代码,使用值类型后,book1
中的 name
属性还是原来的值。也就是说,我们在将 book1 变量赋值给 book2 的时候,是进行的值得赋值,book1 和 book2 分别指向不同的地方。这就是值类型的特性了。
还有什么不同
同样的,引用类型和值类型在传递给方法调用参数的时候的行为也不同,加入我们有这样一个函数:
func rename(var book:Book){ book.name = "star" } |
我们首先将用 class
声明的引用类型的 Book 对象传入到 rename
方法中,然后输出 book.name
属性名:
rename(book) print(book.name) //star |
book
的 name
属性被修改成 rename 方法中的 star 了。因为我们传递给这个方法的是一个引用,rename 方法中根据这个引用修改了它所指向的对象的值。
同样的调用,如果我们传入的 book 是一个用 struct 定义的值类型,就不会再函数调用后修改原来 book 对象的值。因为我们传递给函数 book 参数是一个值类型的复制品,它是不会影响原始对象的。
看完这个,是不是会感觉值类型会更安全些呢,引用类型的对象,很容易就被不知不觉的修改了,很不安全哦。
如果从逻辑的角度来看,好像确实是这样,不过值类型传递有一个问题。如果你的值类型中包含的数据很大的话,比如你的对象里包含一张图片的数据,频繁的调用值类型传递就会很消耗性能。因为每次方法调用,都会把这张图片的数据复制一遍。
所以,这两种引用类型,每一种都不是万能的,要根据你的具体应用情况来考虑。
另外,关于 Swift 中 let 和 var 关键词两种定义,struct 和 class 也有不同的行为。
还是以咱们前面的 Book 类为例,我们用 let 关键字实例化一个用 class 关键字定义的引用类型的 Book:
let book = Book() book.name ="swift" |
我们看到,虽然这个引用是用 let 关键字声明的,我们虽然不能再次给 book 赋值了,但我们还是能够修改 book 的 name 属性。
这也不难理解,let 关键字是对它的符号,也就是 book 来声明的。我们这里的 book 是引用类型,它的引用地址确实不能够再次修改了,但是它里面的属性是否可修改,就不是这个 let 关键字控制的了。
相反,如果我们的 Book 是使用 struct 来定义的,那么 let 关键字就会作用到它的一切,包括它的属性 - 也是不能修改的。
引用类型,值类型,该用哪一个呢?
- 何时使用值类型
引用类型和值类型,都有各自的优势和不足,我们该如何准确的使用它们呢,可以这样参考。
如果它的值比较重要,就使用值类型。比如 CGPoint,两个 CGPoint 实例,如果它们所包含的x,y值相同,就认为他们是相等的:
let pt1 = CGPoint(x: 1, y: 1) let pt2 = CGPoint(x: 1, y: 1) pt1 == pt2 //true |
虽然 pt1 和 pt2 的内存地址不一样,但是他们所包含的 x,y 值是相同的,所以它们是相等的。
另外,如果你期望这个实例所使用的场景是希望每个值都是独立的,互不影响的。还是用 CGPoint 来举例:
let pt = CGPoint(x: 1, y: 1) let view1 = UIView() let view2 = UIView() view1.center = pt1 view2.center = pt1 view1.center = CGPoint(x: 12, y: 5) |
对于上面这个情况,view1 和 view2 最开始都使用 pt1 作为他们的 center 属性。而在某个时候,view1 把它的 center 属性修改了。我们期待的逻辑是 view1 修改它的 center 属性不会影响 view2 的 center 属性,这也需要使用值类型。
想象一下,如果使用引用类型作为 CGPoint 的定义会发生什么情况~
- 何时使用引用类型
对于那些对于内存地址比较重要的类型。还有那些比较大资源类型。比如你定义了一个类型,代表了某个很大的数据,或者资源,比如图片数据,声音数据等等。对于这些数据,往往使用引用类型会对性能有很大的提高。
比起每次传递给方法调用时候都将数据复制一遍,使用引用类型的效率就会高很多。
Swift 本身如何做的
查看一下 Swift 文档,就会发现 Swift 大多数自身的定义,都使用的是 struct,也就是值类型。仔细想一下,也确实有它的道理,毕竟大部分的实例,我们更多考虑的是它的具体的值的情况,而不是它的内存地址。
总的来说,在你明确需要引用类型的地方才使用引用类型,而大多数情况,应该使用值类型。
转自:http://swiftcafe.io/2015/11/17/reference-value/