美文网首页专注iOS开发的小渣渣
swift底层探索 03 - 值类型、引用类型

swift底层探索 03 - 值类型、引用类型

作者: Henry________ | 来源:发表于2020-12-17 20:07 被阅读0次

在swift中所有数据类型的无外乎两种:值类型,引用类型

先上个自己的简答理解:
* 值类型 : 在内存中直接保存值,有点类似oc中targetPoint
* 引用类型 : 在内存中保存指针地址;

1. 内存分区

在了解结构体和值类型之前一定要对内存的五大分区有一个基本的认识,这样才可以对这个概念有一个更准确的认识。

简书-月月
这幅图应该并不陌生,内存地址从低到高分别是:代码区常量区全局区堆区、栈区这五大区域。每一个界限都是一个人为划分和规定的。

通过lldb观察struct、class


  • struct和class初始化之后的内存布局不相同,struct直接存放值,class存放指针。
  • struct的内容直接存放在栈区, class中存放着的指向堆区的指针(猜测),堆区又指针指向的才是class的内容

xcode - Cat address

内存地址属于哪片区域,除了猜测还是有工具可以查看的;
基于lldb-plugin的内存分区查询插件-github

接入
  1. 根目录下关键.lldbinit

    iTerm
  2. 编辑lldbinit文件
    lldbinit文件中,添加语句:

plugin load /Users/***/libfooplugin.dylib(libfooplugin.dylib在你本机的绝对路径)
使用
cat address 0x000000

2. 值类型

结构体

struct PersonModel{
    var name : String = "henry"
    var age : Int = 18
    var phone : Int
}
  • 这就是一个最典型的值类型-结构体。
观察一下结构体的sil文件

swift底层探索 02 - 属性一文中对sil文件的获取和使用做了解释,有兴趣可以去看看。

  • 初始化方法
  • 可以直观的发现,编译器在编译阶段会自动帮我们加上结构体的初始化方法,也会有很多变式有兴趣的可以去看看。
  • 并没有我们熟悉的deinit析构方法
  • 除此之外并没有发现其他不同.
  • Struct ---- init方法
结构体初始化过程-sil
  • 可以看到结构体初始化时,直接把初始化好的值放到了self指向的内存,这也就是值类型的本质,在内存中直接保存值本身,而不是值的指针

  • 而且没有看到对堆内存的申请

  • LLDB + MachOView

前面的结论还是比较抽象的,现在通过LLDB + MachOView,直观的来看下

  • 可以直观的看到age就直接保存在结构体的内存
  • 0x100008180: 0x00000079726e6568 0xe500000000000000这一串不知道是个什么?猜测一下就是Henry,现在通过MachOView来验证一下
顺序问题是结构化输出的原因造成的
  • 通过machOView可以看到这一串就是henry编码后的结果。

使用一个结构体对另一个结构体进行赋值:


  • oldPer赋值给newPer然后修改了旧值,输出的会一致吗?
  • 显然输出是不一致,因为这个值类型赋值只会进行深拷贝(对值进行拷贝),两者之间并没有任何关系,所以任何修改都互不影响。

enum

enum personType {
    case yellow
    case white
    case black
}
  • 简单举例,内存特性和结构体基本一致,就不展开解释了。

[总结]

  • struct只是值类型的一种,除此之外还有enum元组都是值类型
  • 值类型在内存中直接保存具体的值(特别长的字符串除外)。相互赋值也只是对值进行拷贝(深拷贝)。
  • Swift中对值类型增加写时拷贝的特点;赋值后只有只发生变化才会真正的进行拷贝,变化前会保存旧值的指针,这也是一种对内存的优化方案。
  • 可以通俗理解为:值类型相当于是一份文件,相互转发之后,人手一份,任何修改都互不干扰。
  • 保存在栈区,无需处理引用计数

3. 引用类型-class

相比较值类型引用类型应该是无比的熟悉了。比如:NSObject,Class都是引用类型.

初始化方式

  • 我们都知道值类型系统会自动帮我们创建初始化方法。
  • 如果有未确定的值,就需要手动加入初始化方法,否则会报错。这一点和值类型是不同的
sil文件
  • Class
  • 编译器会自动创建init,deinit方法。

  • Class ---- init方法
类的初始化
  • 观察到了堆内存的申请(alloc_ref),以及类应用到堆空间的apply方法.

[总结]

  • 引用类型地址中存在的是指针地址而不是值.
  • 可以通俗理解为:在线表格,谁都可以修改,只要知道地址就可以编辑内存
  • 保存在堆区或全局区,需处理引用计数(ARC)

4. class、struct嵌套使用

引用计数

class teachModel{
    var age : Int = 18
    var age2 : Int = 20
}

struct PersonModel{
    var sub : teachModel
    var name : Int = 20
    var age : Int = 18
    
}
var a = PersonModel(sub: teachModel(), name: 1, age: 2)
var aa = a
  • 使用CFGetRetainCount函数可以看到引用类型的引用计数。也就是说即使将引用类型赋值值类型中,依旧会保持引用计数的管理

内部值的情况

class teachModel{
    var age : Int = 18
}

struct PersonModel{
    var age : Int = 18
    var sub : teachModel = teachModel()
}
var a = PersonModel()
let aa = a
情况一
  • a是值类型,所以修改不会影响其他副本
情况二
  • a.sub是引用类型,所以在深拷贝的时候会把sub的指针进行浅拷贝两个变量中的sub指针指向同一片内存空间,所以修改会导致2者都发生变化。如果理解不了,可以参考OC中的NSString声明需要使用copy关键字。
lldb验证
  • 通过lldb更加直观的看到a的内存布局.
  • 通过lldb更加直观的看到aa的内存布局.

【总结】

  • 需要注意aa声明使用的是let也就是aa是不可变的。但是修改了a导致了aa的修改,这是存在隐患的,开发过程中是需要规避这种情况。

相关文章

网友评论

    本文标题:swift底层探索 03 - 值类型、引用类型

    本文链接:https://www.haomeiwen.com/subject/awangktx.html