在Swift中,提到值类型咱们通常会想到struct,而类是引证类型,那么结构体为什么是值类型,类为什么又是引证类型呢?本文将从结构体和类触发,来探究值类型和引证类型的区别
值类型
-
下面从一个事例来剖析值类型:
func valueTest() { var age1: Int = 18 var age2: Int = 20 var age3: Int = age1 age3 = 26 } valueTest()- 打印三个临时变量的内存地址结果如下:
- 在打印结果中能够看到:
age1和age3的地址不同,且age3赋值后他们值也不同,说明age3 = age1的进程相当于深复制,说明age便是值类型 -
0x7开头的内存代表栈区,且栈区内存是接连的,关于内存方面能够参考 内存分区与布局
结构体
-
下面来界说两个结构体:
struct WSPerson { var age: Int = 18 } struct WSTeacher { var age: Int } // 初始化 var person = WSPerson() var teacher = WSTeacher(age: )- 两个结构体中的成员一个有值,但初始化办法就产生了不同,下面经过
Sil文件检查源码
- 在
Sil文件中WSPerson有两个init函数,其间一个对age进行了赋值,所以当成员变量有值时,能够不对它赋新值。而WSTeacher初始化init办法只有一个,所以两个结构体的初始化办法不相同。
- 两个结构体中的成员一个有值,但初始化办法就产生了不同,下面经过
下面临有初始值类型的struct进行剖析
struct是值类型剖析
-
界说结构体如下:
struct WSPerson { var age1: Int = 18 var age2: Int = 22 } var wushuang = WSPerson() var tony = wushuang tony.age1 = 19 -
再对
tony和wushuang进行Sil剖析- 在
main函数中主要是先进行wushuang的创建,再复制一份给tony,下面再来看看wushuang创建的核心逻辑init
- 创建内存的代码中主要是在
栈区开辟内存,以及对成员变量的处理,所以 结构体是值类型
- 在
总结:
1. 结构体开辟的内存在栈区
2. 结构体的赋值是深复制
引证类型
-
先来看看
class的几种初始化办法:class WSCat { var age: Int init(age: Int) { self.age = age } } class WSDog { var age: Int? } class WSTeacher { var age: Int = 18 } var cat = WSCat(age: 2) var dog = WSDog() var teacher = WSTeacher()
class是引证类型剖析
-
创建一个目标
teacher2,并将teacher赋值给它,打印相关信息结果如下:
值类型嵌套引证类型
-
将代码改成值类型嵌套引证类型,代码如下:
class WSDog { var dogAge: Int = 3 } struct WSPerson { var age: Int = 18 var dog = WSDog() } var person1 = WSPerson() var person2 = person1 person2.dog.dogAge = 5
Mutating & inout
-
在界说结构体时,在结构体的办法中不允许修正实例变量,如下图:
- 将办法里的修正变量值修正下:
struct WSPerson { var age: Int = 0 func add(_ count: Int) { print(count) } }- 生成Sil文件并检查
add办法:
- 在
add办法中,有个let类型的self,也便是此刻的结构体不可变,假设改动age,实质是改动结构体自身,所以在办法中修正成员变量的值会报错。
-
将
self用可变类型接纳,结果不会报错:struct WSPerson { var age: Int = 0 func add(_ count: Int) { var s = self s.age += count } } var person = WSPerson() person.add(3) print(person.age)- 打印结果如下:
- 由于结构体是值类型,所以此刻的
s是深复制,改动的值是s中的,与person目标无关,所以此刻打印依旧是0
-
将办法添上之前报错提示
mutating,此刻就能够修正实例变量的值:- 生成
Sil文件并检查add办法
- 调查发现办法增加
mutating后,有以下改变:-
- 参数中的
WSPerson增加了inout润饰
- 参数中的
-
-
self访问的是地址
-
-
-
self是var可变类型
-
-
- 所以值的修正直接修正的是
person地址,所以能够修正成功
- 生成
-
上面呈现的
inout有什么作用咱们不得而知,下面经过事例来剖析下
办法调度
- 在上面剖析中咱们知道结构体是值类型,那么它的办法在哪呢?下面咱们将对结构体和类的办法存储及调用进行讲解
结构体
-
有如下结构体
struct WSPerson { func speak() { print(" Hello word ") } } let ws = WSPerson() ws.speak() -
在断点检查汇编时,
callq的地址后边显现的是符号,符号都存在字符串表(String Table),能够依据符号表(Symbol Table)中的信息读取,符号表查询进程如下:- 符号在字符串表中的二进制如下:
-
ld和dyld都会在link的时候读取符号表
-
咱们能够运用
nm + MachO途径来检查项目的符号信息: -
能够运用
xcrun swift-demangle + 符号来还原符号:
类
-
下面来看下类的办法调用,先界说一个类及调用办法:
class WSCat { func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } func sleep5() { print(" sleeping 5.. ") } } let ragdoll = WSCat() ragdoll.sleep1() ragdoll.sleep2() ragdoll.sleep3() ragdoll.sleep4() ragdoll.sleep5() -
出产
Sil文件并检查办法:- 在
Sil中的办法次序与汇编中一致,这些办法都存在vtable中,下面咱们去swift源码检查下vtable底层做了什么
- 在
-
在
swift源码中经过查找initClassVTable,得到以下代码:- 主要是经过指针平移获取办法名,并相关
imp,
- 主要是经过指针平移获取办法名,并相关
extension
-
extension中的办法是怎样调度呢?下面界说WSCat类,然后Ragdoll类承继WSCat类class WSCat { func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } } extension WSCat { func sleep5() { print(" sleeping 5.. ") } } class Ragdoll: WSCat { } var cat = Ragdoll() cat.sleep5()-
这
extension中的sleep5办法是怎么调度的呢,咱们知道类里边的办法是经过vtable进行调度,下面出产Sil文件中检查vtable: -
在
Sil能够看到Ragdoll承继了WSCat中其他办法,但并没有sleep5办法。其实这个也比较好理解,假设sleep5也在WSCat的vtable里,那么Ragdoll必定也会承继过来,但假设子类要继续增加办法时,由于办法在vtable中是经过指针平移的办法增加,所以此刻编译器无法确定是在父类增加仍是子类增加,所以是不安全的,那么extension中的办法只能是直接调用,下面打断点检查汇编验证下
-
-
此刻咱们能够得出结论:
extension中的办法调用是直接调用的
总结
-
结构体的办法调度是经过地址直接调用 -
类的办法调度是经过vtable来进行的 -
extension中的办法是直接调用的
final,@objc,dynamic
- 下面研究几个关键字,对办法调度的影响
final
-
下面界说
WSCat类,其间的一个办法运用final润饰class WSCat { final func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } }- 然后结合
Sil和汇编剖析办法调度
- 然后结合
-
所以得出结论:
final润饰的办法是直接调用的
@objc
-
在
WSCat类的其间办法中增加@objc关键字:class WSCat { @objc func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } }- 结合
Sil和汇编剖析:
- 虽然
vtable中有sleep1办法,但是调度办法与上面不同,这种调度办法叫函数表调度
- 结合
-
那么增加
@objc的办法能被OC调用吗?其实不一定,咱们能够先检查混编的头文件 -
类承继
NSObject后,咱们来看看Sil文件有什么改变- 经过调查发现
Sil中有两个sleep1办法,一个给Swift运用,带@objc标记的供给OC运用
- 经过调查发现
dynamic
- 将
WSCat中的一个办法增加dynamic润饰class WSCat { dynamic func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } }- 经过
Sil和汇编剖析得知dynamic润饰的函数调度办法是函数表调度
- 经过
办法交流
-
在
Sil文件的sleep1函数位置,能够看到它被标记为dynamically_replacable- 说明它是动态的可修正的,也便是假设类承继
NSObject,则它能够进行method-swizzling
- 说明它是动态的可修正的,也便是假设类承继
-
Swift中的办法交流需求运用@_dynamicReplacement(for: 调用的函数符号)函数,详细代码如下:class WSCat: NSObject { dynamic func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } } extension WSCat { @_dynamicReplacement(for: sleep1) func eat() { print(" have fish ") } // 交流的函数 } var cat = WSCat() cat.sleep1()- 打印结果如下:
@objc+dynamic
-
在
dynamic的办法前面增加@objc关键字,代码如下:class WSCat: NSObject { @objc dynamic func sleep1() { print(" sleeping 1.. ") } func sleep2() { print(" sleeping 2.. ") } func sleep3() { print(" sleeping 3.. ") } func sleep4() { print(" sleeping 4.. ") } } -
调用
sleep1然后检查汇编:- 结果这个办法的调用办法变成了
objc_msgSend
- 结果这个办法的调用办法变成了
总结
-
struct是值类型,它的函数调度是直接调用,即静态调度- 值类型在函数中假设要修正实例变量的值,则函数前面需求增加
Mutating润饰
- 值类型在函数中假设要修正实例变量的值,则函数前面需求增加
-
class是引证类型,它的函数调度是经过vtable函数,即动态调度 -
extension中的函数是直接调用,即静态调度 -
final润饰的函数是直接调用,即静态调度 -
@objc润饰的函数是函数表调度,假设办法需求在OC中运用,则类需求承继NSObject -
dynamic润饰的函数调度办法是函数表调度,它是动态能够修正的,能够进行method-swizzling-
@objc+dynami润饰的函数是经过objc_msgSend来调用的
-
-
假设函数中的
参数想要被更改,则需求在参数的类型前面增加inout关键字,调用时需求传入参数的地址







































