Swift 中的struct,class以及enum 在一般的使用中能够做到互相替换,因此探究其背后的逻辑就十分有必要。
由此引出Swift中的值类型和引用类型的区别。
Swift 中,值类型,存放在栈区(stack);引用类型,存放在堆区(heap)。
值类型
值类型每个实例保持一份数据拷贝。
Swift中,struct、enum,以及tuple都是值类型。
Swift中,Int、Double、Float、String、Array、Dictionary、Set 其实都是用结构体实现的,也是值类型。
值类型的赋值为深拷贝(Deep Copy),值语义(Value Semantics)即新对象和源对象是独立的,当改变新对象的属性,源对象不会受到影响,反之同理。
如果声明一个值类型的常量,那么就意味着该常量是不可变的(无论内部数据为 var/let),如:
struct CoordinateStruct {
var x: Double
var y: Double
}
let coordA = CoordinateStruct(x: 0, y: 0)
coordA.x = 100.0 // WRONG:无法赋值
var coordB = CoordinateStruct(x: 0, y: 0)
coordB.x = 100.0 // 可以赋值
Swift中,双等号(== & !=)可以用来比较变量存储的内容是否一致,如果要让struct 类型支持该符号,则必须遵守Equatable协议,如:
// 使用==判断是否相等的扩展
extension CoordinateStruct: Equatable {
static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
return (left.x == right.x && left.y == right.y)
}
}
// 判断是否相等
if coordA != coordB {
print("coordA != coordB")
}
当值类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量值,该参数的作用域及生命周期仅存在于函数体内,如:
struct ResolutionStruct {
var height = 0.0
var width = 0.0
func swap(resSct: ResolutionStruct) -> ResolutionStruct {
var resSct = resSct
let temp = resSct.height
resSct.height = resSct.width
resSct.width = temp
return resSct
}
}
var iPhone4ResoStruct = ResolutionStruct(height: 960, width: 640)
// ResolutionStruct(height: 960.0, width: 640.0)
print(iPhone4ResoStruct)
// ResolutionStruct(height: 640.0, width: 960.0)
print(swap(resSct: iPhone4ResoStruct))
// ResolutionStruct(height: 960.0, width: 640.0)
print(iPhone4ResoStruct)
}
引用类型
引用类型的值为数据地址的指针,指向同一地址的所有实例共享一份数据拷贝。
Swift中, class和闭包是引用类型。
引用类型的赋值是浅拷贝(Shallow Copy),引用语义(Reference Semantics)即新对象和源对象的变量名不同,但其引用(指向的内存空间)是一样的,因此当使用新对象操作其内部数据时,源对象的内部数据也会受到影响。
如果声明一个引用类型的常量,那么就意味着该常量的引用不能改变(即不能被同类型变量赋值),但指向的内存中所存储的变量是可以改变的,例如:
class Dog {
var height = 0.0
var weight = 0.0
}
var dogA = Dog()
let dogC = Dog()
dogC.height = 50 // 可以修改属性值
dogC = dogA // WRONG:无法赋值
Swift中,三等号(=== & !==)可以用来比较引用类型的引用(即指向的内存地址)是否一致。也可以在遵守Equatable协议后,使用双等号(== & !=)用来比较变量的内容是否一致,例如:
// 使用===判断指向的内存地址是否相同
if (dogA === dogB) {
print("dogA === dogB")
}
// 使用==判断是否相等的扩展
extension Dog: Equatable {
static func ==(left: Dog, right: Dog) -> Bool {
// 判断内容是否一致
return (left.height == right.height && left.weight == right.weight)
}
}
// 判断内容是否一致
if dogC == dogA {
print("dogC == dogA")
}
当引用类型的变量作为参数被传入函数时,相当于创建了新的常量并初始化为传入的变量引用,当函数体内操作参数指向的数据,函数体外也受到了影响,如:
class ResolutionClass: CustomStringConvertible {
var height = 0.0
var width = 0.0
var description: String {
return "ResolutionClass(height: \(height), width: \(width))"
}
func swap(resCls: ResolutionClass) {
let temp = resCls.height
resCls.height = resCls.width
resCls.width = temp
}
}
let iPhone5ResoClss = ResolutionClass()
iPhone5ResoClss.height = 1136
iPhone5ResoClss.width = 640
// ResolutionClass(height: 1136.0, width: 640.0)
print(iPhone5ResoClss)
swap(resCls: iPhone5ResoClss)
// ResolutionClass(height: 640.0, width: 1136.0)
print(iPhone5ResoClss)
inout关键字
inout可以放置于参数类型前,冒号之后。使用inout 之后,函数体内部可以直接更改参数值,而且改变会保留,如:
引用类型也可以使用
inout参数,但意义不大。
struct ResolutionStruct {
var height = 0.0
var width = 0.0
func swap(resSct: inout ResolutionStruct) {
let temp = resSct.height
resSct.height = resSct.width
resSct.width = temp
}
}
var iPhone6ResoStruct = ResolutionStruct(height: 1334, width: 750)
// ResolutionStruct(height: 1334.0, width: 750.0)
print(iPhone6ResoStruct)
swap(resSct: &iPhone6ResoStruct)
// ResolutionStruct(height: 750.0, width: 1334.0)
print(iPhone6ResoStruct)
注意:
- 使用 inout 关键字的函数,在调用时需要在该参数前加上 & 符号
- inout 参数在传入时必须为变量,不能为常量或字面量(literal)
- inout 参数不能有默认值,不能为可变参数
- inout 参数不等同于函数返回值,是一种使参数的作用域超出函数体的方式
- 多个 inout 参数不能同时传入同一个变量,因为拷入拷出的顺序不定,那么最终值也不能确定
inout 参数的传递过程:
1、当函数被调用时,参数值被拷贝
2、在函数体内,被拷贝的参数修改
3、函数返回时,被拷贝的参数值被赋值给原有的变量
inout参数的本质与引用类型的传参并不是同一回事。
inout 参数打破了其生命周期,是一个可变浅拷贝。
嵌套类型
值类型和引用类型可以相互嵌套,可以分为四种情况,下面 简要介绍这四种嵌套类型:
1、值类型嵌套值类型
值类型嵌套值类型时,赋值时创建了新的变量,两者是独立的,嵌套的值类型变量也会创建新的变量,这两者也是独立的。
2、值类型嵌套引用类型
值类型嵌套引用类型时,赋值时创建了新的变量,两者是独立的,但嵌套的引用类型指向的是同一块内存空间,当改变值类型内部嵌套的引用类型变量值时(除了重新初始化),其他对象的该属性也会随之改变。
3、引用类型嵌套值类型
引用类型嵌套值类型时,赋值时创建了新的变量,但是新变量和源变量指向同一块内存,因此改变源变量的内部值,会影响到其他变量的值。
4、引用类型嵌套引用类型
引用类型嵌套引用类型时,赋值时创建了新的变量,但是新变量和源变量指向同一块内存,内部引用类型变量也指向同一块内存地址,改变引用类型嵌套的引用类型的值,也会影响到其他变量的值。