Swift类与结构体

213 阅读6分钟

相关文档

类和结构体的定义


class ClassTeacher{

var age:Int

var name:String

init(age:Int, name:String) {

self.age = age

self.name = name

}

deinit{

}

}

结构体

struct StructTeacher{

var age:Int

var name:String

init(age:Int, name:String) {

self.age = age

self.name = name

}

}

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

相同点
  • 定义存储值的属性

  • 定义方法

  • 定义下标以使用下标语法提供对其值的访问

  • 定义初始化器

  • 使用extension来拓展功能

  • 遵循协议来提供某种功能

不同点
  • 类有继承的特性,而结构体没有

  • 类型转能够在运行时检查和解释实例的类型

  • 类有析构函数用来释放其分配的资源

  • 引用计数允许对一个实例有多个引用

  • 类是引用类型,意味着一个类类型的变量并不直接存储具体的实便对象,是对当前存储具体实例内存地址的引用。

  • Struct是值类型,值类型存储的就是具体的实例

  • 一般情况下,值类型存储在栈上,而引用类型存储在堆上

值类型和引用类型
  • 值类型:即每个变量保持一份数据拷贝,每个变量值改变不会影响其它变量,值拷贝即深拷贝,常见的值类型有Struct、enum、tuple、Double、Int等

  • 引用类型:所有变量中保存的地址是相同的,即共享一份数据。当修改数据时,所有的变量值也会受影响。即是指针拷贝也就是浅拷贝。在Swift中常见引用类型有class和closure

类和结构体的存储位置

image.png

  • 栈区:存储局部变量各函数运行过程的上下文,栈区是一块连续的内存,一般是从高地址--> 低地址进行存储

  • 堆区:存储所有对象堆区是不连续的内存(便于增删,不便于查询),一般是从低地址--> 高地址进行存储

  • 全局静态区:存储全局变量和静态变量,该区是编译时分配的内存空间,程序运行过程中,内存数据一直存在,程序结束后由系统释放

  • 常量区:存储常量:整型,字符串等

  • 代码区:存储程序被编译成的二进制

* 类和结构体的选择和使用

在Swift开发过程中一般情况下优先使用结构体。当然需要根据你自己项目的功能模块,能使用结构体的情况下优先使用结构体。比如封装一些简单的Model,不需要继承的某个类这种情况下就可以使用结构体。

  • 结构体是值类型一般分配在栈区,栈区的内存是连续的,当栈指针指到要运行到需要给结构体开辟内时,它会根据结构体大小在栈上开辟一块内存空间,然后将结构体中的值拷贝到栈中,当函数执行完以后栈空间会自动回收内存自动释放,所以性能消耗低,速度快

  • 类是引用类型,内存分配是在堆区,堆区的内存是不连续的,不便于查找。首先需要到堆中找到一块可用的内存,然后返回内存地址存放在堆区。当内存释放时,会根据栈区存放的内存地址去堆区查找释放。这个过程性能消耗较大,速度慢

类和结构体的初始化器

结构体初始化器

结构体的初始化器一般分为两种系统自动生成的成员初始化器和自定义初始化器

结构体默认初始化器

如果结构体中有储存属性,而且没有自定义的成员变量初始化器。那么编译时系统会自动给你生成一个成员变量初始化器。

image.png

自定义结构体初始化器

struct Person{

var age: Int = 10

var name: String = "ch"

init(_ age: Int, _ name: String){

self.age = age

self.name = name

}

}

let p = Person(10,"ch")

如果自定义结构体初始化器,那么编译器不会在自动生成其它的初始化器。 结构体中的属性声明时可以不用赋值,但是自定义初始化器中,必须对每个属性都进行赋值,否则编译器会报错

类初始化器

编译器默认不会为类提供成员初始化器 

image.png

类默认初始化器

类默认会提供一个初始化器init(){},但是的默认的初始化器不会为任何属性赋值,所以在类中的属性声明时必须要为所有的属性设置一个初始值。代码如下


class Person{

var age: Int = 10

var name: String = "ch"

}

let p = Person()

自定义类初始化器

因为要给类中所有的属性设置一个合适的初始值,所以中必须要提供指定的初始化器。代码如下


class Person{

var age: Int

var name: String

//指定初始化器

init(_ age: Int, _ name: String){

self.age = age

self.name = name

}

}

let p = Person(10,"ch")

便捷初始化器

指定的初始化器一般情况下只有一个,相当于暴露对外的接口,只能通过这个指定的初始化器去初始化所有属性,虽然指定初始化器只有一个,但是便捷初始化器可以有很多(注意的是:便捷初始化器必须从相同的类里调用另一个初始化器)代码如下


class Person{

var age: Int

var name: String

init(_ age: Int, _ name: String){

self.age = age

self.name = name

}

convenience init(_ age: Int){

//self.name = "abc" 在初始化完成前对其属性赋值或者把self作为值使用是不允许的

self.init(age, "ch")

self.name = "abc"

}

convenience init(_ name: String) {

self.init(20, name)

}

}

let p = Person.init(10)

image.png

继承关系初始化器的注意点

class Person{

var age: Int

var name: String

init(_ age: Int, _ name: String){

self.age = age

self.name = name

}

}

class Teacher: Person{

var height:Float

init(_ height: Float){

self.height = height

super.init(10, "ch")

//这种写法是错误的,指定初始化器必须保证自己的所有属性都被初始化在委托给父类之前

//self.height = height;

}

}

let t = Teacher.init(185.0)

可失败初始化器

意味着当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。这种 Swift 中可失败初始化器写 return nil 语句,来表明可失败初始化器在何种情况下会触发初始化失败。写法也非常简单


class Person{

var age: Int

var name: String

init?(_ age: Int,_ name: String){

if age < 18 {return nil}

self.age = age

self.name = name

}

}

let p = Person(17,"ch")

必要初始化器

在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器

image.png

初始化器总结
  • 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成

  • 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖

  • 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖

  • 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例 属性的值,也不能引用 self 作为值

类的生命周期

iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的

  • OC 通过clang编译器,编译成IR,然后再生成可执行文件.o(机器码)

  • Swift则是通过Swift编译器编译成IR,然后在生成可执行文件

Swift代码生成可执行文件的详细过程

image.png

  • Swift 源码经过-dump-parse(词法分析) 生成Parse即抽象

  • Parse经过-dump-ast(语法分析)生成Sema检查语法是否正确(编译的时候如果有问题会报错)

  • Sema经过降级生成SILGen,即SIL中间代码

  • SILGen经过-emit-silgen生成Raw SIL,即原生的SIL

  • Raw SIL经过-emit-sil生成SILOpt Canonical SIL,即优化后的SIL

  • SILOpt Canonical SIL 降级生成IRGen,即IR中间代码

  • IRGen 经过-emit-ir生成IR,最后变成机器码

常用swiftc分析输出命令

swiftc main.swift -dump-parse // 分析输出AST

swiftc main.swift -dump-ast // 分析并且检查类型输出AST

swiftc main.swift -emit-silgen // 生成中间体语言(SIL),未优化

swiftc main.swift -emit-sil // 生成中间体语言(SIL),优化后的

swiftc main.swift -emit-ir // 生成LLVM中间体语言 (.ll文件)

swiftc main.swift -emit-bc // 生成LLVM中间体语言 (.bc文件)

swiftc main.swift -emit-assembly // 生成汇编

swiftc -o main.o main.swift // 编译生成可执行.out文件

Swift 对象内存分配
  • 类初始化分两步,申请开局内存,通过init方法初始化完成所有属性

*__allocating_init -----> swift_allocObject -----> swift_allocObject -----> swift_slowAlloc -----> Malloc

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

  • metadata是一个指针类型所以里面存放的是一个地址,metadata类似OC中的isa,refCounts是引用计数

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

}