结构体
结构体的常用写法
//***** 写法一 *****
struct QTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
var t = QTeacher()
//***** 写法二 *****
struct QTeacher {
var age: Int
func teach(){
print("teach")
}
}
var t = QTeacher(age: 18)
- 在结构体中,如果不给属性默认值,编译是不会报错的。即在结构体中属性可以赋值,也可以不赋值

- init方法可以重写,也可以使用系统默认的
结构体的SIL分析
- 如果没有init,系统会提供默认初始化方法

- 如果自定义的init,就只显示自定义的init方法

结构体是值类型
- 定义结构体,打印结构体对象
struct QTeacher {
var age: Int = 18
var name: String = "teacher"
}
var t = QTeacher()
print("end")
- 发现t的打印直接就是值,没有任何与地址有关的信息

-
获取t的内存地址,并查看其内存情况
- 获取地址:po withUnsafePointer(to: &t){print($0)}
- 查看内存情况: x/4gx 0x0000000100008180

- 此时将t赋值给t1,如果修改了t1,t会发生改变吗?
import Foundation
struct QTeacher {
var age: Int = 18
var name: String = "teacher"
}
var t = QTeacher()
var t1 = t
t1.age = 14
print("end")
- 直接打印t及t1,可以发现t并没有因为t1的改变而改变,主要是因为因为t1和t之间是值传递,即t1和t是不同内存空间,是直接将t中的值拷贝至t1中。t1修改的内存空间,是不会影响t的内存空间的

结构体的方法调度 : 静态派发
- 声明t变量,调用teach方法,并在调用方法处打断点

- 进入汇编调试

- callq命令是调用的意思,结构体在调用方法时,直接调用的是地址,那说明在编译时期结构体的方法已经确定好了地址,在调用时直接调用其地址。

类
类的常用写法
//****** 写法一 *******
class QTeacher {
var age: Int = 18
func teach(){
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t = QTeacher.init(20)
//****** 写法二 *******
class QTeacher {
var age: Int?
func teach(){
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t = QTeacher.init(20)
- 在类中,如果属性没有赋值,也不是可选项,编译会报错

- 需要自己实现init方法
类是引用类型
定义一个QTeacher1类,并查看对象t1
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
}
var t1 = QTeacher1()
- 对象t1,存储在全局区,打印出来的是一个指针地址

-
获取t1变量的地址,并查看其内存情况
- 获取t1指针地址:po withUnsafePointer(to: &t1){print($0)}
- 查看t1全局区地址内存情况:x/4g 0x0000000100008358
- 查看t1地址中存储的堆区地址内存情况:x/4g 0x0000000100509790

引用类型 特点
-
1、地址中存储的是堆区地址
-
2、堆区地址中存储的是值
此时将t1赋值给t2,如果修改了t2,会导致t1的修改
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
}
var t1 = QTeacher1()
var t2 = t1
t2.age = 180

类的方法调度
动态派发
- 声明test1方法,调用,并查看汇编
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
var t1 = QTeacher1()
t1.test1()
-
汇编指令补充
blr:带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址mov:将某一寄存器的值复制到另一寄存器(只能用于寄存器与起存起或者 寄存器与常量之间 传值,不能用于内存地址)mov x1, x0将寄存器x0的值复制到寄存器x1中
ldr:将内存中的值读取到寄存器中ldr x0, [x1, x2]将寄存器x1和寄存器x2 相加作为地址,取该内存地址的值翻入寄存器x0中
str:将寄存器中的值写入到内存中str x0, [x0, x8]将寄存器x0的值保存到内存[x0 + x8]处
bl:跳转到某地址
-
查看sil源码,sil_vtable

sil_vtable:关键字
QTeacher:表示是QTeacher类的函数表
其次就是当前方法的声明对应着方法的名称
函数表 可以理解为 数组,声明在 class内部的方法在不加任何关键字修饰的过程中,是连续存放在我们当前的地址空间中的。
静态派发
extension中的函数中,方法的调用是直接调用地址。
- 声明QTeacher1的extension,调用test4方法,并查看汇编
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
extension QTeacher1{
func test4(){
}
}
var t1 = QTeacher1()
t1.test4()
- 直接调用的地址

- V-Table中不存在test4方法

子类中的方法
- 新建QTeacher2类,继承于QTeacher1
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
extension QTeacher1{
func test4(){
}
}
class QTeacher2 : QTeacher1{
}
var t1 = QTeacher1()
t1.test4()
print("end")
-
查看sil
- QTeacher2中继承了QTeacher1中的v-table中的方法
- 但是QTeacher1中的extension方法是没有继承的

小结
- 继承方法和属性,不能写extension中。
- 而extension中创建的函数,一定是只属于自己类
final、@objc、dynamic、inout、mutaing关键字
final
final 修饰的方法是 直接调度的,可以通过 断点验证
- 方法test1前面添加final关键字修饰
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
final func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
var t1 = QTeacher1()
t1.test1()
- 进入符号断点,直接进行调度

@objc
使用@objc关键字是将swift中的方法暴露给OC
- 方法test1前面添加@objc关键字修饰
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
@objc func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
var t1 = QTeacher1()
t1.test1()
- 进入符号断点,发现是函数表调度

- 如果只是通过
@objc修饰函数,OC还是无法调用swift方法的,因此如果想要OC访问swift,class需要继承NSObject
<!--swift类-->
class QTeacher: NSObject {
@objc func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
override init(){}
}
<!--桥接文件中的声明-->
SWIFT_CLASS("_TtC9_3_指针10QTeacher")
@interface QTeacher : NSObject
- (void)teach;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
<!--OC调用-->
//1、导入swift头文件
#import "QOCTest-Swift.h"
//2、调用
QTeacher *t = [[QTeacher alloc] init];
[t teach];
- 查看SIL文件发现被@objc修饰的函数声明有两个:swift + OC(内部调用的swift中的teach函数)

dynamic
dynamic是方法具有动态性
- 方法test1前面添加dynamic关键字修饰
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
dynamic func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
var t1 = QTeacher1()
t1.test1()
print("end")
-
进入符号断点,发现是函数表调度;
- 其中test1函数的调度还是 函数表调度,可以通过断点调试验证,使用dynamic的意思是可以动态修改,意味着当类继承自NSObject时,可以使用method-swizzling

- 其中test1函数的调度还是 函数表调度,可以通过断点调试验证,使用dynamic的意思是可以动态修改,意味着当类继承自NSObject时,可以使用method-swizzling
@objc + dynamic
class QTeacher1 {
var age: Int = 18
var age2: Int = 20
@objc dynamic func test1(){
self.age = 19
print(self.age)
}
func test2(){}
func test3(){}
}
var t1 = QTeacher1()
t1.test1()
print("end")
- 通过断点调试,走的是objc_msgSend流程,即 动态消息转发

- swift中实现方法交换
在swift中的需要交换的函数前,使用dynamic修饰,然后通过:@_dynamicReplacement(for: 函数符号)进行交换,如下所示

inout
一般情况下,在函数的声明中,默认的参数都是不可变的,如果想要直接修改,需要给参数加上inout关键字
- 添加inout关键字,可以给参数赋值

mutaing
一般使用在结构体中, 通过结构体定义一个栈,主要有push、pop方法,此时我们需要动态修改栈中的数组
- 如果是以下这种写法,会直接报错,原因是值类型本身是不允许修改属性的

- 将push方法改成下面的方式,查看SIL文件中的push函数

- 给push添加mutaing,发现可以添加到数组了

- 查看其SIL文件,找到push函数,发现与之前有所不同,push添加mutaing(只用于值类型)后,本质上是给值类型函数添加了inout关键字,相当于在值传递的过程中,传递的是引用(即地址)

本文主要介绍了Swift中结构体和类的相关知识。结构体是值类型,方法调度为静态派发;类是引用类型,方法调度有动态和静态派发。还讲解了final、@objc、dynamic、inout、mutaing等关键字的作用,如final修饰方法直接调度,@objc将方法暴露给OC等。
1万+

被折叠的 条评论
为什么被折叠?



