Swift 类与结构体(上)

2,555 阅读6分钟

1、初识类与结构体

Swift 中类用class修饰,结构体用struct修饰

class ClassSwagger{
    var age:Int
    var name:String
    
    init(age:Int, name:String) {
        self.age = age
        self.name = name
    }
    deinit{
        
    }
}
struct StructSwagger{
    var age:Int
    var name:String
    init(age:Int, name:String) {
        self.age = age
        self.name = name
    }
}

1.1 结构体和类的相同点不同点

相同点

  • 定义存储值的属性
  • 定义方法
  • 定义下标以使用下标语法提供对其值的访问
  • 定义初始化器
  • 可以使用 extension 来拓展功能
  • 遵循协议来提供某种功能

主要的不同点

  • 类有继承的特性,而结构体没有
  • 类型转换使您能够在运行时检查和解释类实例的类型
  • 类有析构函数用来释放其分配的资源
  • 引用计数允许对一个类实例有多个引用

1.2 类和结构体最本质的区别

类是引用类型,结构体是指类型

我们可以通过lldb 指令来查看当前变量的内存结构

po:只会输出对应的值

p:返回值的类型以及命令结果 的引用名

x/8g:读取内存中的值(8g: 8字节格式输出)

frame variable -L 打印对象的内存布局

  • 类类型的引用

一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用,如图所示

截屏2022-01-24 14.45.38.png

截屏2022-01-24 14.26.32.png

截屏2022-01-24 14.26.48.png

截屏2022-01-24 14.27.03.png

s 和 s1 的地址是相同的,且两个变量引用对象的内存地址完全相同符合引用类型的结果。

  • 结构体是值类型

结构体是一种值类型,内存分配在栈中。复制后新的实例和值类型成员属性的地址空间都是重新分配的,引用类型的成员属性仍然指向之前的地址空间

截屏2022-01-24 14.47.55.png

截屏2022-01-24 14.32.24.png

ct 和 ct1 的地址是不相同的,且每个变量的内存地址中存储的属性也是不同的地址,说明两个指向的是两个实例对象

存储区域的不同

引用类型和值类型还有一个最直观的区别就是存储的位置不同: 一般情况,值类型存储的在栈上,引用类型存储在堆上

  • 对于类的内存分配
    1. 在栈上开辟 8 字节内存空间存储 s 变量,s1 变量中存储 ClassSwagger 的地址
    2. 在堆上,会寻找合适的内存区域,开辟内存,存储 ClassSwagger 的实例对象
    3. 函数结束时,会销毁栈上的指针,查找并回收堆上的示例变量
  • 对于结构体的内存分配
    1. 在栈上直接分配结构体内存大小的空间存储结构体的值

2、初始化器

初始化器是为了可以完整地初始化实例,所以在初始化器中必须完成对存储属性的赋值

2.1 默认初始化器

  • 编译器不会自动提供类的初始化器,需要自己提供一个指定初始化器 截屏2022-01-24 14.55.20.png
  • 结构体来说编译器会提供默认的初始化方法(前提是我们自己没有指定初始化器)! 截屏2022-01-24 14.56.44.png

2.2 指定初始化器

  • 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器,类偏向于少量指定初始化器,一个类通常只有一个指定初始化器 截屏2022-01-24 14.59.34.png
  • 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成 截屏2022-01-24 15.09.50.png

截屏2022-01-24 15.10.06.png

  • 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值

截屏2022-01-24 15.10.32.png

截屏2022-01-24 15.10.45.png

2.3 类的便利初始化器

便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)

截屏2022-01-24 15.16.31.png 截屏2022-01-24 15.16.42.png

2.4 可失败初始化器

当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况 截屏2022-01-24 15.17.38.png

2.5 必要初始化器

在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器 截屏2022-01-24 15.19.53.png 截屏2022-01-24 15.20.07.png

3、类的生命周期

3.1 Swift的编译

iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如下图所示:

swiftc.jpeg

  • OC 通过clang编译器,编译成IR,然后再生成可执行文件 .o(这里也就是我们的机器码)
  • Swift则是通过Swift编译器编译成IR,然后在生成可执行文件 swiftc2.jpeg

3.2 Swift源码分析

Swift源码

搜索HeapObject.cpp文件找到 swift_allocObject 函数可以看到内部调用了swift_slowAlloc函数

heapob.jpeg 进入swift_slowAlloc 函数可以看到调用了 malloc

heapob2.jpeg 由此可以得出 swift对象创建时经过如下的过程分配内存的__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc

Swift对象的内存结构 HeapObject (OC objc_object) 有两个属性: 一个是 Metadata ,一个是 RefCount,默认占用16字节大小

heapob3.jpeg

4、类的结构窥探

在OC 中类的只有一个 isa 指针,但是在 swift 中有 HeapMetadata 和 refCounts 两个属性

4.1 Metadata源码分析

Metadata的继承关系及结构简述

查看Metadata的源码可以看到Metadata是HeapMetadata 类型这是 TargetHeapMetadata 定义的别名 meta1.jpeg

查看 TargetHeapMetadata 可得它继承自 TargetMetadata,同时也可以知道 其初始化时如果是 纯swift类则传入 MetadataKind kind 如果是和 OC 交互的类传入 isa meta2.jpeg

TargetMetadata 中可得到一个 kind 的成员变量 meta4.jpeg

MetadataKind 的解析

查看 MetadataKind 可得它是一个 uint32_t的类型 meta5.jpeg 在 swift 中 MetadataKind有如下类型 kind.jpeg

4.2 类的结构分析

源码分析猜想

上面说到了 Metadata 最终继承自 TargetMetadata,且其成员中的 kind 对应多种类型,所以猜测 Metadata 最终会在这里完成创建 通过源码分析可以看到下面的这个函数

struct1.jpeg 可以看出 这里会根据 kind 的种类 将TargetMetadata 转换为其他类型的 Metadata ,所以 TargetMetadata 可能就是所有类型的元类型的基类

当 kind 的 是Class 时会将 this 强转为 TargetClassMetadata 类型 这个类型可能就是类的最终结构,所以分析它的结构就应该可以得到类的大致结构

 case MetadataKind::Class: {
      const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);

进入 TargetClassMetadata 其继承自TargetAnyClassMetadata struct2.jpeg

struct3.jpeg 最终通过对源码的追踪可以发现类的结构有如下继承关系

TargetClassMetadata : TargetAnyClassMetadata :TargetHeapMetadata : TargetMetadata

经过以上分析,可以大致得出swift类的结构如下

struct Metadata{ 
   var kind: Int
   var superClass: Any.Type
   var cacheData: (Int, Int)
   var data: Int
   var classFlags: Int32
   var instanceAddressPoint: UInt32
   var instanceSize:  UInt32
   var instanceAlignmentMask: UInt16
   var reserved: UInt16
   var classSize: UInt32
   var classAddressPoint: UInt32
   var typeDescriptor: UnsafeMutableRawPointer 
   var iVarDestroyer: UnsafeRawPointer
}

强转结构验证

上面分析得到类的实例对象结构是 HeapObject 有两个属性: 一个是 Metadata ,一个是 RefCount

所以我们自定义一个结构体,然后将我们的实例对象强转为该类型,如果转换成功则说明分析正确

截屏2022-01-24 16.38.23.png 截屏2022-01-24 16.38.08.png

结果说明

  1. 上面分析的实例对象结构为HeapObject类型
  2. metadata 可以转换成上述分析的Metadata类型