Swift学习笔记 - 文集
一、结构体
在 Swift 中,绝大多数公开的类型都是结构体类型。我们在swift学习笔记 ① —— 基础语法中讲到,Swift 提供了值类型和引用类型两种数据类型,结构体就属于值类型。而Int、Bool、Double、Array、Dictionary等常见类型都属于结构体类型。
和 OC 一样,我们通过关键字 struct 来定义结构体,也可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。:
struct Point {
var x: Int
var y: Int
}
所有的结构体都有一个编译器自动生成可以传入成员值的初始化器,我们在使用结构体时,传入所有存储属性值,用以初始化所有存储属性。例如:
var point = Point(x: 10, y:20)
编译器为了保证所有成员都有初始值,也会根据实际情况生成不同的初始化器。例如在声明结构体时,我们设置了其中一个成员的初始值,在使用结构体时就不用再设置初始值:
struct Point {
var x: Int = 0
var y: Int = 0
}
// x y 已经设置了初始值,可以不传入初始值
var point = Point()
// x y 已经设置了初始值,也可以继续传入新的值
var point1 = Point(x: 10, y:20)
//可以单独传入x 或 y 的初始值
var point2 = Point(x: 10)
var point3 = Point(y: 20)
如果我们在定义结构体时,自定义了初始化器,那么编译器就不会再为我们生成初始化器:
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
例子中我们自定义了初始化器,所以在使用时需要传入初始值:
var point = Point(x: 10, y:20)
如果没有传入所有成员的初始值那么编译就会报错:
var point1 = Point()
var point2 = Point(x: 10)
var point3 = Point(y: 20)
结构体的内存结构
我们可以通过 MemoryLayout
来分析一下结构体的内存结构:
struct Point {
var x: Int = 0
var y: Int = 0
var origin: Bool = true
}
print(MemoryLayout.size) // 17
print(MemoryLayout.stride) // 24
print(MemoryLayout.alignment) // 8
可以看出,结构体Point
实际占用内存为 17 个字节,在内存中共占用 24 个字节,内存对齐是 8 个字节。
类
Swift中,类和结构体很相似。我们可以为类定义属性(常量、变量)和方法。
class Size {
var width: Int = 0
var height: Int = 0
}
上面的例子中我们就定义了 Point
类,里面包括两个属性。
需要注意的是,编译器并没有为类自动生成可以传入成员值的初始化器,但是因为x
和y
有初始值,所以编译器为类生成了一个无参的初始化器。所以我们可以通过无参的方式调用:
var size1 = Size()
由于编译器并没有为类自动生成可以传入成员值的初始化器,所以下面的调用会报错:
var size2 = Size(width: 10)
var size3 = Size(height: 20)
var size4 = Size(width: 10, height:20)
结构体和类的区别
两者的区别就是:结构体是值类型,类是引用类型(指针类型)。
例如下面的代码:
struct Point {
var x: Int = 10
var y: Int = 20
}
class Size {
var width: Int = 30
var height: Int = 40
}
var point = Point()
var size = Size()
由于结构体是值类型,那么例子中的变量 point
在栈空间中占据的 16 个字节的内存空间保存的就是 x
和y
的值;而类是引用类型,变量 size
是一个指针变量,在栈空间中占据 8 个字节内存,保存的是指针变量 size
指向的内存地址,也就是 Size()
类对象的内存地址。
Size()
类对象保存在堆空间中,占据了 24 个字节的内存空间。其保存的数据除了width
和height
两个成员的值外,还保存着指向类型的相关信息和引用计数。
下面我们通过 MemoryLayout
来打印一下size
对象的内存:
var size = Size()
print(MemoryLayout.stride(ofValue: size)) // 8
print(MemoryLayout.size(ofValue: size)) // 8
print(MemoryLayout.alignment(ofValue: size)) // 8
可以看出,size
对象实际占用内存为 8 个字节,在内存中共占用 8 个字节,内存对齐是 8 个字节。
我们通过第三方工具Mems
再来看一下变量point
和size
分别保存了什么:
print("size变量的内存内容:", Mems.memStr(ofVal: &size))
print("point变量的内存内容:", Mems.memStr(ofVal: &point))
打印输出:
size变量的内存内容: 0x0000000100706d40
point变量的内存内容: 0x000000000000000a 0x0000000000000014
可以看出,size
变量保存的是一个内存地址,而point
变量保存的则是初始值 10 和 20。
我们再来看一下size
变量保存的内存地址指向的内存保存的内容:
print("size所指向内存的内容", Mems.memStr(ofRef: size))
打印输出:
size所指向内存的内容 0x00000001000086e8 0x0000000200000002 0x000000000000001e 0x0000000000000028
可以看出,32 个字节中一共存储了 4 中内容。保存的具体内容是:最开始的 8 个字节保存了 指向类型的相关信息,其次 8 个字节保存的是引用计数,最后两个 8 个字节内存分别存储了width
和height
两个成员的值 30 和 40 。
值类型
值类型赋值给变量或常量,或者给作为参数传给函数时,是直接将内容拷贝一份,类似作copy
操作,产生一个全新的副本,属于深拷贝。
在 Swift 中,为了提升性能,String
、Array
、Dictionary
、Set
都采取了 Copy On Write
技术,只有存在“写”操作时,才会真正执行拷贝操作。
例如:
var name1 = "Mars"
var name2 = name1
name2.append("_iOS")
print(name1) // Mars
print(name2) // Mars_iOS
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 5
print(a1) // [5, 2, 3]
print(a2) // [1, 2, 3, 4]
引用类型
不同于值类型,引用类型赋值给变量或常量,或者给作为参数传给函数时,是直接将内存地址拷贝一份,指向同一块内存,属于浅拷贝。
例如:
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
print(s1.width) // 11
print(s1.height) // 22
print(s2.width) // 11
print(s2.height) // 22
可以看出,我们在修改变量s2
的值时,变量s1
的值也发生了改变。这就是因为指针变量s1
和s2
指向的是同一块内存,当修改s2
时,变量s1
和s2
指向的内存空间所保存的数据发生改变,自认打印s1
的值也会发生改变。
更多技术知识请扫码关注微信公众号
iOS进阶