值类型假设:需要了解内存5大空间。 内存5大区域请参考本文的iOS-基础原理24 :内存5大区域。 以下所示
值类型-1
堆栈区域的地址大于堆区域的地址
堆栈从高地址到低地址向下延伸,由系统自动管理,是连续的内存空间
堆从低地址-高地址向上延伸,由程序员管理,堆空间结构像链表一样不连续
日常开发中的溢出是指堆栈溢出,可以理解为堆栈区域和堆栈区域的边界冲突的情况
全局区域和常量区域都存储在Mach-O的__TEXT cString段中
作为一个例子,我们引入什么是值类型
functest((/堆栈区域声明存储从age变量var age=18 //传递的值var age2=age //age的地址。 age2是指更改独立内存中的值age=30age2=45print ) )。
从age的存储器状况来看,从图中可以看出,堆栈区域直接容纳了值
获取age的堆栈地址: powithunsafepointer(to:age ) print ($0) ) ) ) ) ) ) ) ) ) ) ) )。
查看age内存状态: x/8g0x00007ffeefbff3e0
值类型-2
从age2的情况来看,从下图可以看出,age2的代入相当于取出age的值代入到age2中。 这里,由于age和age2地址相差8字节,所以可知堆栈空间是连续的,从高到低
值类型-3
因此,正如上面所解释的,age是值类型
值类型特征
1、地址中存储有值
2、在传递值类型的过程中,相当于传递了一个副本,即所谓的深度副本
3、交接值过程中,不共享状态
结构的一般写法
//*****写法1 * * * * structcjlteacher (varage : int=18 func teach () (print ) ) }}var t=CJLTeacher ) structcjlteacher { varage : intfuncteach () print('teach'}}vart=cjlteacher ) age:18 )是结构中属性的默认值
值类型-4
可以重写init方法,也可以使用系统缺省的
结构的SIL分析
如果没有init,则缺省初始化方法不同
值类型-5
如果提供了定制的init,则只有定制的
值类型-6
为什么结构是值类型? 定义和分析结构
structcjlteacher (varage : int=18 varage 2: int=20 ) vart=cjlteacher )打印(' end ' )打印t:po t,从下图中可以看到t
值类型-7
获取t的存储器地址,确认其存储器状况
地址获取: powithunsafepointer(to:t ) print ($0) ) ) ) ) ) )。
查看内存状态: x/8g0x0000000100008158
值类型-8
问:此时,将t代入t1。 修改t1后,t会发生变化吗?
直接打印t和t1,可以看出t没有随t1的变化而变化。 主要是因为t1和t之间是值的传递,即t1和t是不同的内存空间,直接将t的值复制到t1。 t1修改的存储器空间不影响t的存储器空间
值类型-9
SIL认证
同样,也可以通过分析SIL来验证结构是否为值类型
在SIL文件中,查看如何初始化结构,可以看到只有init,没有malloc,没有看到任何堆分配
>值类型-10 总结结构体是值类型,且结构体的地址就是第一个成员的内存地址
值类型
在内存中直接存储值
值类型的赋值,是一个值传递的过程,即相当于拷贝了一个副本,存入不同的内存空间,两个空间彼此间并不共享状态
值传递其实就是深拷贝
引用类型 类**类的常用写法 **
//****** 写法一 *******class CJLTeacher { var age: Int = 18 func teach(){ print("teach") } init(_ age: Int) { self.age = age }}var t = CJLTeacher.init(20)//****** 写法二 *******class CJLTeacher { var age: Int? func teach(){ print("teach") } init(_ age: Int) { self.age = age }}var t = CJLTeacher.init(20)在类中,如果属性没有赋值,也不是可选项,编译会报错
引用类型-1
需要自己实现init方法
为什么类是引用类型?定义一个类,通过一个例子来说明
class CJLTeacher1 { var age: Int = 18 var age2: Int = 20}var t1 = CJLTeacher1()类初始化的对象t1,存储在全局区
打印t1、t:po t1,从图中可以看出,t1内存空间中存放的是地址,t中存储的是值
引用类型-2
获取t1变量的地址,并查看其内存情况
获取t1指针地址:po withUnsafePointer(to: &t1){print($0)}
查看t1全局区地址内存情况:x/8g 0x0000000100008218
查看t1地址中存储的堆区地址内存情况:x/8g 0x00000001040088f0
引用类型-4
引用类型 特点
1、地址中存储的是堆区地址
2、堆区地址中存储的是值
问题1:此时将t1赋值给t2,如果修改了t2,会导致t1修改吗?
通过lldb调试得知,修改了t2,会导致t1改变,主要是因为t2、t1地址中都存储的是 同一个堆区地址,如果修改,修改是同一个堆区地址,所以修改t2会导致t1一起修改,即浅拷贝
引用类型-5
问题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 = tt1.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
引用类型-6
注意:在编写代码过程中,应该尽量避免值类型包含引用类型
查看当前的SIL文件,尽管CJLTeacher1是放在值类型中的,在传递的过程中,不管是传递还是赋值,teacher都是按照引用计数进行管理的
引用类型-7
可以通过打印teacher的引用计数来验证我们的说法,其中teacher的引用计数为3
引用类型-8
主要是是因为:
main中retain一次
teacher.getter方法中retain一次
teacher.setter方法中retain一次
引用类型-9
mutating通过结构体定义一个栈,主要有push、pop方法,此时我们需要动态修改栈中的数组
如果是以下这种写法,会直接报错,原因是值类型本身是不允许修改属性的
引用类型-10
将push方法改成下面的方式,查看SIL文件中的push函数
struct CJLStack { var items: [Int] = [] func push(_ item: Int){ print(item) }}引用类型-11
从图中可以看出,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关键字,给参数赋值,编译报错
引用类型-14
添加inout关键字,可以给参数赋值
总结
1、结构体中的函数如果想修改其中的属性,需要在函数前加上mutating,而类则不用
2、mutating本质也是加一个 inout修饰的self
3、Inout相当于取地址,可以理解为地址传递,即引用
4、mutating修饰方法,而inout 修饰参数
总结通过上述LLDB查看结构体 & 类的内存模型,有以下总结:
值类型,相当于一个本地excel,当我们通过QQ传给你一个excel时,就相当于一个值类型,你修改了什么我们这边是不知道的
引用类型,相当于一个在线表格,当我们和你共同编辑一个在先表格时,就相当于一个引用类型,两边都会看到修改的内容
结构体中函数修改属性, 需要在函数前添加mutating关键字,本质是给函数的默认参数self添加了inout关键字,将self从let常量改成了var变量