Swift 类与结构体

文章详细介绍了Swift中的类和结构体的差异,包括它们的内存分布、初始化器的使用规则、方法的区别以及方法调度机制。类作为引用类型,其实例存在于堆内存,而结构体作为值类型,通常存储在栈内存。此外,文章还探讨了类和结构体的初始化过程,以及如何通过初始化器设置默认值。在方法调度方面,解释了Swift如何进行静态派发和函数表调度。
摘要由CSDN通过智能技术生成

目录

一、初识类和结构体

// 类
class PersonClass : NSObject {
	var name: string
	var age: Int

	init(age: Int, name: string) {}

	// 析构函数
	deinit {}
}

// 结构体
struct PersonStruct {
	var name: string
	var age: Int

	init(age: Int, name: string) {}
}

在这里插入图片描述

1.1 Class(引用类型)

var p = PersonClass(age: 10, name: "张三")
var p1 = p

在这里插入图片描述

类不管在栈、堆、全局数据区等定义,所定义的类存储的数据就一定在堆空间,而指针变量的内存是存在相应的栈、堆、全局数据区

po: p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果
的引用名。
x/8g: 读取内存中的值(8g: 8字节格式输出)

1.2 Struct(值类型)

var p = PersonStruct(age: 10, name: "张三")
var p1 = p

请添加图片描述

结构体在栈、堆、全局数据区等定义,所定义的结构体存储的数据也是存在相应的栈、堆、全局数据区

1.3 内存分布

引用类型 与 值类型内存分布位置不同:一般情况,值类型分布在栈上,引用类型分布在堆上。
请添加图片描述

Stack(栈区):局部变量和函数运行过程中的上下文(函数)
Heap(堆区):存储所有对象
Global(全局区):存储全局变量
常量区:字符串 & 常量
代码区:存储指令

Mach-O 文件有多个段(Segment),每个段有不同的功能。然后每 个段又分为很多小的 Section
TEXT.text : 机器码
TEXT.cstring : 硬编码的字符串
TEXT.const: 初始化过的常量
DATA.data: 初始化过的可变的(静态/全局)数据
DATA.const: 没有初始化过的常量
DATA.bss: 没有初始化的(静态/全局)变量
DATA.common: 没有初始化过的符号声明

二、结构体 & 类初始化器

2.1 结构体(Struct)

结构体初始化器:编译器会根据情况,会为结构体生成多个初始化器,宗旨是保证所有成员都有初始值

// 结构体
struct Point {
  var x: Int 
  var y: Int
}
var p1 = Point(x: 10, y: 10)var p2 = Point(x: 10)var p3 = Point(y: 10)var p4 = Point()struct Point {
  var x: Int = 0
  var y: Int
}
var p1 = Point(x: 10, y: 10)var p2 = Point(x: 10)var p3 = Point(y: 10)var p4 = Point()struct Point {
  var x: Int = 0
  var y: Int = 0
}
var p1 = Point(x: 10, y: 10)var p2 = Point(x: 10)var p3 = Point(y: 10)var p4 = Point()// 可选项有个默认的值nil
struct Point {
  var x: Intvar y: Int}
var p1 = Point(x: 10, y: 10)var p2 = Point(x: 10)var p3 = Point(y: 10)var p4 = Point()

结构体自定义初始化器:一旦在定义结构体时自定义初始化器,编译器就不会再帮它自动其他生成初始化器

struct Point {
  var x: Int = 0
  var y: Int = 0
  init(x: Int, y: Int) {
    self.x = x
    self.y = y
  }
}

var p1 = Point(x: 10, y: 10)var p2 = Point(x: 10)// 虽然有初始值,但是编译器不会帮你生成
var p3 = Point(y: 10)// 虽然有初始值,但是编译器不会帮你生成
var p4 = Point()// 虽然有初始值,但是编译器不会帮你生成

2.2 类(Class)

类初始化器:类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
(类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器,成员的初始化是在这个初始化器中完成的)

class Point {
  var x: Int = 0
  var y: Int = 0
}

let p1 = Point()var p2 = Point(x: 10)var p3 = Point(y: 10)var p4 = Point(x: 10, y: 10)
  1. 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性
    都要初始化完成。
  2. 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如
    果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
  3. 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括
    同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其
    它指定初始化器所覆盖。
  4. 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例
    属性的值,也不能引用 self 作为值。

可失败初始化器:意味着当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。

// 示例
class Person {
	let age: Int
	let name: String

	init?(age: Int, name: String){
		if age < 6 {
			return nil
		}
		self.age = age
		self.name = name
	}
}

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

// 示例
class Person {
	let age: Int
	let name: String

	// 必要初始化器
	required init(age: Int, name: String){
		self.age = age
		self.name = name
	}
}

// 示例
class Student: Person {
	let subjectName: String
	required init(age: Int, name: String){
		super.init(age: age, name: name)
		self.subjectName = "语文"
	}
}

2.3 对象的堆空间申请过程

swift中,在创建类的实例对象,需要向堆空间申请内存(malloc,alloc指令代表分配内存在堆空间),流程如下:

  1. Class.__allocating_init()
  2. libswiftCore.dylib:swift_allocObject
  3. libswiftCore.dylib:swift_slowAlloc
  4. libsystem_malloc.dylib:malloc

Mac,iOS中的malloc函数分配的内存大小总是16的倍数

通过class_getInstanceSize可以得知类的对象真正使用的内存大小

class Point {
	var x = 11
	var test = true
	var y = 22
}
var p = Point()
class_getInstanceSize(type(of: p))
class_getInstanceSize(Point.self)

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

三、结构体 & 类方法区别

Class 与 Struct 都能定义方法,但值类型属性不能被自身的实例方法修改

struct Point {
    var x = 0.0, y = 0.0
    func moveBy(x deltaX: Double, y deltaY: Double) {
        //self - 提示值类型属性 x,y 不能被实例方法修改 ❎
        x += deltaX
        y += deltaY
    }
}

可通过查看 SIL 文件来查看添加与否 mutating 修饰词实例方法的差异:

sil hidden [ossa] @$s4main5PointV4testyyF : $@convention(method) (Point) -> Void
debug_value %0 : $Point, let, name "self", argno 1 // id: %1

// 未使用 mutating 修饰传递的是 let point = self

sil hidden [ossa] @$s4main5PointV6moveBy1x1yySd_SdtF : $@convention(method) (@inout Point) -> Void
debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5

// 使用 mutating 修饰传递的是 var point = &self (传递地址,并且是 var,可修改的)

异变方法的本质:对于变异方法, 传入的 self 被标记为 inout 参数。无论在 mutating 方法内部发生什么,都会影响外部依赖类型的一切。
输入输出参数:函数能够修改一个形式参数的值,而且希望这些改变在函数结束之后依然生效,那么就需要将形式参数定义为 输入输出形式参数,(inout关键字可以定义一个输入输出形式参数)

四、方法调度

OC 方法调用是采用 objc_msgSend,而Swift 类型方法调度,是采用函数表调度

class JHTest {
    func test() {}
}

// 函数调用:-> metedata -> 函数地址(metedata + 偏移量)
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
}

// typeDescriptor: Class, Struct, Enum 都有自己的类描述
struct TargetClassDescriptor{
    var flags: UInt32
    var parent: UInt32
    var name: Int32
    var accessFunctionPointer: Int32
    var fieldDescriptor: Int32  // 属性信息
    var superClassType: Int32
    var metadataNegativeSizeInWords: UInt32
    var metadataPositiveSizeInWords: UInt32
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32
    var Offset: UInt32
    var size: UInt32

    //V-Table(函数表)
}

方法调度总结

类型调度方式拓展(extension )
值类型静态派发(编译时函数地址已确定)静态派发(编译时函数地址已确定)
函数表派发静态派发(猜测原因:之前类型已经确定了,如果在其他类拓展类,静态调用,不会改变原类的结构)
NSObject 子类函数表派发静态派发

影响函数的派发方式

  • final: 添加了 final 关键字的函数无法被重写,使用静态派发,不会在 vtable 中出现,且
    对 objc 运行时不可见。
  • dynamic: 函数均可添加 dynamic 关键字,为非objc类和值类型的函数赋予动态性,但派发
    方式还是函数表派发。
  • @objc: 该关键字可以将Swift函数暴露给Objc运行时,依旧是函数表派发。
  • @objc + dynamic: 消息派发的方式
  • static / class:静态派发

函数内联: 是一种编译器优化技术,它通过使用方法的内容替换直接调用该方法,从而优
化性能。

  • 将确保有时内联函数。这是默认行为,我们无需执行任何操作. Swift 编译器可能会自动内
    联函数作为优化。
  • always - 将确保始终内联函数。通过在函数前添加 @inline(__always) 来实现此行为
  • never - 将确保永远不会内联函数。这可以通过在函数前添加 @inline(never) 来实现。
  • 如果函数很长并且想避免增加代码段大小,请使用@inline(never)(使用@inline(never))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值