值类型和引用类型在Swift中的使用
前言 值类型 vs 引用类型
1、什么是值类型
值类型就是值直接保存在变量中。例如:
1 | int a = 10; |
值类型赋新值的时候会直接覆盖旧值。
1 | // 1 |
按注释:
1.这段代码首先声明了一个int
类型的变量b
,然后将a
中保存的值赋值给b
。
2.给b
赋新值,不会影响a
中保存的值。
2、什么是引用类型
引用类型,变量中保存的是地址,地址指向实际的对象。例如:
1 | NSString *foo = @"hello"; |
引用类型变量重新赋值的时候会改变变量中保存的地址,新的地址会覆盖旧的地址,并且新的地址指向(引用)新创建的对象。
1 | NSMutableString *foo = [NSMutableString stringWithString:@"helllo, "]; |
1、声明了一个新的变量bar
,bar
中保存的地址等于foo
中保存的地址,因此它们指向同一个对象。
2、当修改bar
指向的对象时,foo
指向的对象也被修改,因为它们俩引用的是同一个对象。
一、Swift中的值类型和引用类型
相对于OC,Swift 的优点主要有安全、快速、简洁。Swift的安全性主要体现在,它会在编译时就确定要调用的方法和属性(静态优化),而不是像OC一样到运行时才确定调用哪个方法。Swift会在编译时就告诉你,你的代码是否有bug(比如访问了野指针,数组越界等),而OC会在运行时才发现这些错误并造成APP的crash。
类似于OC中的类,Swift中的类都是引用类型,结构体、枚举、元组都是值类型。
值类型的特点就是拷贝,假如我们创建了一个值类型的实例,当它作为参数传递给函数或者赋值给常量或变量时,这个实例就会生成一份它自身的拷贝(unique copy),当对这个实例进行赋值、参数传递等操作时,实际上被赋值或者传递的是拷贝的那一份,无论怎么修改都不会改变该实例本身。而引用类型的实例进行赋值、参数传递等操作时,实际上传递的就是它本身,我们在函数中修改的就是它自身。类似于 C 函数中的值传递和引用传递。
1、引用类型
1 | // Reference type example |
上述代码的内存如下图所示,变量x
和y
指向同一个类C
的实例。
2、值类型
1 | // Value type example |
内存如下图所示,对实例进行赋值操作时,b = a
,会将a
的拷贝赋值给b
,而不是a
本身,因此,a
和b
保存了两个不同的实例,对其中一个的修改不会影响另一个(下图箭头并不是指针的意思,为了表示清楚一点用了箭头)。
值类型拷贝操作的时间复杂度为基于被拷贝数据量大小的O(n)。当值类型的实例传递到函数中时,实际上是声明了一个局部变量,它的作用域仅仅是函数的内部,所以当出了函数时,这份拷贝的实例就会被释放,因此不用担心它的空间复杂度或者拷贝浪费内存的问题。
3、可变性
对于值类型和引用类型,关键字var
和let
是有区别的。对于引用类型来说,let
的意思是,引用必须是常量,换句话你说,你不能修改常量指向其他实例,即不能修改常量中保存的地址,但是你可以修改实例本身。
1 | let x = C() |
对于值类型来说,let
的意思是实例必须是常量,而常量也是不变的,因此不能修改常量中保存的值,也不能修改值的本身。
1 | let a = S() |
因此,相对于引用类型来说,值类型能更好控制实例可变和不可变,例如NSString
和NSMutableString
,只需要将字符串声明为let a = " "
,那么它就是不可变类型的字符串,声明为var a: String?
,它就是可变字符串。(Swift中的String为值类型)
4、如何选择
If you always get a unique, copied instance, you can trust that no other part of your app is changing the data under the covers. This is especially helpful in multi-threaded environments where a different thread could alter your data out from under you. This can create nasty bugs that are extremely hard to debug.
如果你一直希望持有一个实例的一份独立的备份,即使APP在其他地方修改了这部分数据,你仍然能访问一份原始数据。这在多线程的环境中特别有用,如果其他子线程在你不知道的情况下修改了这个实例,那么你仍然持有一份最原始的数据的备份。
使用值类型的情况:
- 使用
==
比较两个实例时 - 当你需要一份独立的备份时
- 多线程中可能会修改数据时
使用引用类型的情况:
- 使用
===
比较两个实例时 - 你需要共享并且修改这个类型的实例时
==(Equal To)
表示两个实例的值相等(注意,值类型变量中保存的是值不是地址);
===(Identical To)
表示两个实例完全相等,包括它们在内存中的地址也相同。
在Swift中,Array `
String Dictionary
IntBool
等数据类型都是值类型,换句话说都是
struct而不是
class`。因此在对它们赋值、传参等操作时,切记它是值传递,而不是引用传递。
1 | func foo(a: [String]) |
二、enum、struct、class
Swift的类型系统:
Named type(命名类型)是指在定义时,可以使用特定的名字去命名的类型。例如:
1 | let foo: Double = 0.0 |
Named model type(命名模型类型)是指可以在声明的时候可以重写setter
或者getter
的类型。例如:
假如你有一个存储半径的变量,然后要声明一个存储直径的变量:
1 | var radius = 5.0; |
Compound type(复合类型)是指没有特定命名的类型。例如:
1 | // declare a tuple |
1、enum
(1)、初始值
枚举类型的每一个选项都可以带有一个初始值,和OC中的枚举类型一样,不同的是,Swift允许你指定这个初始值的类型,并且给每一个选项赋值。
1 | enum Color: String { |
(2)关联值
Swift中的枚举类型的每一个选项都允许你传入一个变量作为它的关联值,你可以在switch
它的时候使用这个变量。例如:
1 | enum CSColor { |
*(3)Optional
Optional
在Swift中首先是个神奇的东西,其次是个非常重要的东西,理解它对于学习Swift重中之重。
它的声明:
1 | enum Optional<T> { |
Optional
类型其实是一个枚举类型,它只有两个选项,要么是none
,要么是某个T
类型的值some
。
(I)关于?
?
的意思是声明的变量是个Optional
类型。例如:
1 | var foo: Double? |
声明一个Double类型的变量,可以不赋值或者赋初值为nil
,否则必须初始化它,如果一个变量不是Optional
类型,那它永远不可能是nil
。
当我们在使用?
解包的时候,它的执行过程应该为(伪代码):
1 | // 1 |
1 | // 1 伪代码 |
如果bar = nil
,则返回nil
,如果bar != nil
,则返回bar
的值,并且调用lowercased
方法。当然返回值赋值给x
,x
也是Optional
类型,因为它也可能为nil
。
(II)关于!
!
的作用是强制解包的意思(unwrap),即,将Optional
类型的变量强制解包为某个类型的值。如果这个值为nil
,仍然强制解包的话,应用将会在运行时crash。所以如果不能确定变量的值是一定不是nil
的话,千万不要用!
解包。这种方式可以保证应用不会crash:
1 | var bar: String? |
(III)关于nil
OC中的nil
代表空指针的意思,就是C中的NULL
,它的定义为:
1 | // MacType.h |
1 | // C |
在OC中我们可以向nil
发送消息(调用方法),因为OC是一门动态的语言,在编译期它并不关心一个对象是不是nil
,它只关心这个类型的对象能不能接收这条消息。它会在运行时判断消息的接收者是不是nil
,如果是nil
,它会根据消息的返回类型返回对应的值,例如要求返回int
类型,返回值就是0
,BOOL
类型,返回值就是NO
,id
类型,返回值就是nil
,函数将直接返回。
Swift中的nil
,并不是空指针的意思,因为Swift并不是一门纯面向对象的语言,大大减少了对引用类型的依赖,使用较多的是值类型,因此空指针对值类型来说,并没有什么意义。因此,nil
表示的是没有值(the absence of a value)即,这块内存的这个变量中没有保存任何值。所以它在Swift中的定义为:
1 | typealias nil = Optional.none |
2、struct & class
Swift中的类都是引用类型,结构体都是值类型。
Comparing Classes and Structures (类和结构体的异同点)
Classes and structures in Swift have many things in common. Both can (共同点):
Define properties to store values. (声明属性)
Define methods to provide functionality. (声明方法)
Define subscripts to provide access to their values using subscript syntax. (使用点语法)
Define initializers to set up their initial state. (使用构造方法初始化)
Be extended to expand their functionality beyond a default
implementation. (扩展默认实现以外的功能)
Conform to protocols to provide standard functionality of a certain kind. (使用协议)
For more information, see Properties, Methods, Subscripts, Initialization, Extensions, and Protocols. (更多信息)
Classes have additional capabilities that structures do not (类比结构体多的能力):
Inheritance enables one class to inherit the characteristics of another. (类可以继承)
Type casting enables you to check and interpret the type of a class instance at runtime. (类型转换使您能够在运行时检查和解释实例的类型)
Deinitializers enable an instance of a class to free up any resources it has assigned. (提供析构方法来释放一个类占用的资源)
Reference counting allows more than one reference to a class instance. (引用计数允许存在多个对实例的引用)