Swift-类与结构体(1)

 

一、类与结构体

在这里,我们主要探究在Swift中类与结构体之间的相同与不同,以及在我们平时使用的过程中应该如何选择使用。

(1)结构体的特性:

 

struct Student1{

    var age:Int

    var name:String

}

let s1 = Student1(age: 19, name: "swift")

 

struct Student2{

    var age:Int = 0

    var name:String = "swift"

}

let s2 = Student2()



 

  • 结构体中提供的默认的构造函数 init(age:Int,name:string) 当我们构造一个结构体实体p时就会借助这个默认构造器来初始化,当然构造函数也可以自己定义,在自己指定的构造器中需要对未初始化的属性进行赋值,如果我们在定义属性时就给定了初始化值,可以直接通过let s2 = Student2()来进行初始化。
  • 通过如下的代码看出,结构体是一种值类型,分配在栈区,从s = s1中可以看出,s和 s1的地址不同,说明结构体是在栈区重新分配的,但是如果结构体中有引用类型的成员变量,则在复制时不会重新分配。
struct Student1{

    var age:Int

    var name:String

}

var s1 = Student1(age: 19, name: "swift")
var s = s1

 

 

  •  从地址首位0x00000可以看出s,s1均位于栈区。
  • 结构体第一个成员的地址就是结构体的首地址(结构体在栈中是连续的)

(2) 类特性

类是一种引用类型,他分配在堆区(不连续,第一个成员的地址和类的首地址不一致)中,也就是说一个类类型的变量不是直接存储具体的实例对象,而是存储当前具体实例的内存地址的引用,复制后的实例和成员属性的值都是指向原有的地址空间。

class Student1{
    var age:Int
    var name:String
}//Class 'Student1' has no initializers

(类不会默认生成成员初始化器)我们需要知道在声明类的属性时 ,我们如果既没有初始化方法又没有给类的属性定义初始值时,编译器会报错。我们需要在定义类时给定init方法或者为类的存储属性定义初始值。初始化器在初始化前不可以调用任何实例方法、不能读取任何属性的值、不能引用self作为值

class Student1{
    var age:Int
    var name:String
    //直接赋值
    var age:Int?
var name:String?
//指定初始化器 init(age:Int,name:String){ self.age = age self.name = name } //便捷初始化器 convenience init(){ self.init(age:19,name:"swift") } //可失败的初始化器 init?(age:Int,name:String){ if age < 18{ return nil } self.age = age self.name = name } //必要初始化器 required init(age:Int,name:String){ self.age = age self.name = name } }
  • 第一种:可以通过对存储属性直接赋值来初始化
  • 第二种:指定初始化器,通常指定初始化器只有一个 可携带0或多个参数
  • 第三种:编辑初始化器,编辑初始化器可以有多个,在编辑初始化器中必须调用相同类型的初始化器
  • 第四种:可失败的初始化器,主要针对初始化失败的情况,在可失败的初始化器中可写return nil语句
  • 第五种:必要初始化器:这个关键字要求所有的子类必须实现该初始化器

那么在继承时,初始化器又是如何的呢?

class Student:Person{
    var subAge:Int
    init(subAge:Int){
        self.subAge = subAge
        super.init(age: 10)
    }
}
  • 子类的构造函数必须调用父类的构造函数
  • 子类的指定初始化器中需要先初始化自己的属性,再初始化父类的指定初始化器
  • 子类如果要修改继承属性,必须在调用父类的初始化器后进行,否则继承属性就会被父类的初始化器所覆盖

(3)类和结构体的不同

从最直观的角度来讲,类和结构体最显著的区别在于类是引用类型,而结构体是值类。

相同点

  • 定义存储属性
  • 定义方法
  • 定义初始化器
  • 遵循协议来提供某种功能
  • 都可使用extension来扩展功能(2)中会讲到
  • 可以定义下标通过下标语法来提供对其值的访问

不同点

  • 类有继承,结构体没有
  • 类有析构函数用来释放其分配的资源
  • 引用计数允许对一个类实例有多个引用
  • 在类型转化时可以在运行时检查和解释类实例的类型

 二、类的生命周期

  • OC通过clang编译器,编译成IR,再生成可执行文件
  • swift通过swift编译器编译成IR,再生成可执行文件
  • swift code的编译过程中会生成SIL文件(swift的中间代码)(四讲)
  • swift的底层实现机制可以通过lldb调试看到(比如swift对象内存分配的实现 ,在五中我们会展开讲解)
  • swift对象的内存结构HeapObject(OC对象的内存结构为OC objc_object),swift(16字节)包含两个属性:MetaData +RefCount;OC(8字节)只包含一个属性:isa指针

三、补充知识

  • frame varibale - L +变量名---->输出变量的内存地址
  • 内存区域了解:(我们可用的区域是从0x00000-0x00007,大致可分为栈区和堆区,对于堆区又可分为全局区、常量区、代码区)
  • po 输出值 p返回值的类型以及命令结果的引用名
  •  x/8g:读取内存中的值(8g代表8字节格式输出
  • 引用好比一个在线文档
  • 我们一般情况下都是用结构体,因为结构体在栈区分配较快
  • 在lldn调试时,可使用cat address + 对象 即可探索地址所在空间

四、SIL文件

这部分内容我也是第一次了解,下面以swift的对象内存分配为例:

我们在项目的TARGTETS-XXScript-Build Phases的shell脚本中输出 swiftc -emit-sil ${SRCROOT}/LLSwiftTest/main.swift > ./main.sil & open main.sil 即可生成并打开SIL文件

 

五、lldb调试查看swift对象内存分配过程

在项目的debug-debug workflow-always show中会看到我们在生成一个类实例时调用的函数

 

 通过对这行打断点按住control可继续向下进入这个函数调用内部

由此我们推断出:__allocating_init -----> swift_allocObject -----> swift_allocObject -----> swift_slowAlloc -----> Malloc(swfit对象内存分配过程)

 

posted on 2022-01-12 16:43  suanningmeng98  阅读(90)  评论(0)    收藏  举报