类
- 类的常用写法
//写法一
class PDTeacher {
var age:Int
func teach() {
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t = PDTeacher.init(20)
//写法二
class PDTeacher2 {
var age:Int?;
func teach() {
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t1 = PDTeacher2.init(12)
- 在类中,如果属性没有赋值,也不是可选项,编译时会报错,需要自己实现
init方法
为什么类时引用类型?
定义一个类,通过一个例子说明
class CJLTeacher1 {
var age: Int = 18
var age2: Int = 20
}
var t1 = CJLTeacher1()
类初始化的对象t1,存储在全局区
-
打印t1、t:
po t1,从图中可以看出,t1内存空间中存放的是地址,t中存储的是值 -
获取t1变量的地址,并查看其内存情况
-
获取t1指针地址:
po withUnsafePointer(to: &t1){print($0)} -
查看t1全局区地址内存情况:
x/8g 0x0000000100008218 -
查看t1地址中存储的堆区地址内存情况:
x/8g 0x00000001040088f0
引用类型 特点
- 1、地址中存储的是
堆区地址 - 2、
堆区地址中存储的是值
问题1:此时将t1赋值给t2,如果修改了t2,会导致t1修改吗?
- 通过
lldb调试得知,修改了t2,会导致t1改变,主要是因为t2、t1地址中都存储的是同一个堆区地址,如果修改,修改是同一个堆区地址,所以修改t2会导致t1一起修改,即浅拷贝
问题2:如果结构体中包含类对象,此时如果修改t1中的实例对象属性,t会改变吗?
代码如下所示
class CJLTeacher1 {
var age: Int = 18
var age2: Int = 20
}
struct CJLTeacher {
var age: Int = 18
var age2: Int = 20
var teacher: CJLTeacher1 = CJLTeacher1()
}
var t = CJLTeacher()
var t1 = t
t1.teacher.age = 30
//分别打印t1和t中teacher.age,结果如下
t1.teacher.age = 30
t.teacher.age = 30
从打印结果中可以看出,如果修改t1中的实例对象属性,会导致t中实例对象属性的改变。虽然在结构体中是值传递,但是对于teacher,由于是引用类型,所以传递的依然是地址
同样可以通过lldb调试验证
- 打印t的地址:
po withUnsafePointer(to: &t){print($0)} - 打印t的内存情况:
x/8g 0x0000000100008238 - 打印t中teacher地址的内存情况:
x/8g 0x000000010070e4a0注意 在编写代码过程中,应该尽量
避免值类型包含引用类型
查看当前的SIL文件,尽管CJLTeacher1是放在值类型中的,在传递的过程中,不管是传递还是赋值,teacher都是按照引用计数进行管理的
可以通过打印teacher的引用计数来验证我们的说法,其中teacher的引用计数为3
主要是是因为:
-
main中retain一次 -
teacher.getter方法中retain一次 -
teacher.setter方法中retain一次
mutating
通过结构体定义一个栈,主要有push、pop方法,此时我们需要动态修改栈中的数组
- 如果是以下这种写法,会直接报错,原因是
值类型本身是不允许修改属性的 - 将push方法改成下面的方式,查看SIL文件中的push函数
struct CJLStack {
var items: [Int] = []
func push(_ item: Int){
print(item)
}
}
从图中可以看出,push函数除了item,还有一个默认参数self,
self是let类型,表示不允许修改
- 尝试1:如果将push函数修改成下面这样,可以添加进去吗?
struct CJLStack {
var items: [Int] = []
func push(_ item: Int){
var s = self
s.items.append(item)
}
}
打印结果如下
可以得出上面的代码并不能将item添加进去,因为s是另一个结构体对象,相当于值拷贝,此时调用push是将item添加到s的数组中了
- 根据前文中的错误提示,给push添加mutating,发现可以添加到数组了
struct CJLStack {
var items: [Int] = []
mutating func push(_ item: Int){
items.append(item)
}
}
查看其SIL文件,找到push函数,发现与之前有所不同,push添加mutating(只用于值类型)后,本质上是给值类型函数添加了inout关键字,相当于在值传递的过程中,传递的是引用(即地址)
inout关键字
- 一般情况下,在函数的声明中,默认的参数
都是不可变的,如果想要直接修改,需要给参数加上inout关键字 - 未加
inout关键字,给参数赋值,编译报错 - 添加
inout关键字,可以给参数赋值
总结
-
1、结构体中的函数如果想修改其中的属性,需要在函数前加上mutating,而类则不用
-
2、mutating本质也是加一个 inout修饰的self
-
3、Inout相当于取地址,可以理解为地址传递,即引用
-
4、mutating修饰方法,而inout 修饰参数
总结
通过上述LLDB查看结构体 & 类的内存模型,有以下总结:
-
值类型,相当于一个本地excel,当我们通过QQ传给你一个excel时,就相当于一个值类型,你修改了什么我们这边是不知道的
-
引用类型,相当于一个在线表格,当我们和你共同编辑一个在先表格时,就相当于一个引用类型,两边都会看到修改的内容
-
结构体中函数修改属性, 需要在函数前添加mutating关键字,本质是给函数的默认参数self添加了inout关键字,将self从let常量改成了var变量