Swift类与结构体(上)中了解了类与结构体的区别,并且分析了swift类的结构。接下来让我们窥探一下类与结构体中的方法。
1、异变方法 mutating
在 swift 中值类型的修改属性是会直接修改实例的内存,相当于修改自身的值,所以值类型的属性是不能被自身的方法修改的,需要加上 mutating
关键字修饰
为什么加上 mutating
之后就可以修改自身的值呢?通过下面的代码来窥探一下 mutating
的原理
sil分析
通过下面命令,生成 sil 文件
swiftc -emit-sil ViewController.swift > ./ViewController.sil
//需要将 UIKit 相关内容编译成 sil 时使用
swiftc -emit-sil -target x86_64-apple-ios15.0-simulator -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) ViewController.swift > ./ViewController.sil
struct StructSwagger{
var sex: Bool = false
var age: Int = 18
func test(_ age:Int){
var age = age
age = 20
print(age)
}
mutating func test1(_ newage:Int){
age += newage
}
}
- test函数
- test1函数
两个方法对比说明
这两个方法最明显的有以下两点
- 两个函数的第一个框标记说明,实例函数默认传入 self 参数的类型有区别
test
传入的是StructSwagger
类型,是实例对象本身test1
传入的是@inout StructSwagger
类型,`是实例对象的地址
- 从两个函数的第二个框标记可以发现,函数底层声明的行参类型不同
inout
参数会声明一个var
常量- 普通的参数则都是
let
常量 这两种区别,可以通过如下伪代码表示
//test1
let self = StructSwagger
//test1
var self = &StructSwagger
总结:值类型中的属性都是直接存储在实例中,所以在方法内部修改属性相当于修改 self,而 self 在非mutating
的实例方法中传入的是值本身,并且默认是通过 let 修饰,所以无法修改;在mutating
实例方法中传入的 self 被标记为 inout
参数,这里传入的 self 是传入了实例的指针,并且是用 var 来声明,所以才可以在内部修改值类型的属性。
2、类的实例方法
2.1 汇编验证
从 x0 开始分析,由于 x0 寄存器一般用来存储函数的返回值,所以 x0 可能是 __alloc_init
函数的返回值,也就是实例对象,将断点设置在 mov x20, x0
的位置,通过 register read
可以读取存储的值,得到确实是实例对象的 metaldata
所以 x0 ,x20 中都是类的实例对象
(lldb) register read x0
x8 = 0x00000001005e8ab0 type metadata for Swagg3r.Swagger
再看 ldr x8 [x20]
获取了 x20
中的值读取到 x8
中 也是就说拿到实例对象的metadata
存储到 x8
,最后通过 实例对象的metadata
一个偏移量再获取到函数地址进行调用,通过上面的汇编分析可得三个框的位置即是三个方法调用的位置,我们可以清晰的看到,三个方法的偏移量为 0x50
,0x58
,0x60
是三个连续的位置
总结:类的实例方法是通过 metadata 然后通过一个偏移量找到方法地址进行调用
汇编常见指令
- cbz: 和 0 比较,如果结果为零就转移(只能跳到后面的指令)
- cbnz: 和非 0 比较,如果结果非零就转移(只能跳到后面的指令)
- cmp: 比较指令
- br: 跳转到某地址(无返回)
- blr: 跳转到某地址(有返回)
- ret: 子程序(函数调用)返回指令,返回地址已默认保存在寄存器 lr (x30) 中
- mov: 将某一寄存器的值复制到另一寄存器(只能用于寄存器与寄存器或者寄存器 与常量之间传值,不能用于内存地址),如:
mov x1, x0 将寄存器 x0 赋值到寄存器 x1 ˙中
- add: 将某一寄存器的值和另一寄存器的值 相加 并将结果保存在另一寄存器中, 如:
add x0, x1, x2 将寄存器 x1 加 x2 的值赋值到 x0
- sub: 将某一寄存器的值和另一寄存器的值 相减 并将结果保存在另一寄存器中:
sub x0, x1, x2 将寄存器 x1 减 x2 的值赋值到 x0
- and: 将某一寄存器的值和另一寄存器的值 按位与 并将结果保存到另一寄存器中, 如:
and x0, x0, #0x1 将寄存器 x0 的值和常量 1 按位与后赋值到 x0
- orr: 将某一寄存器的值和另一寄存器的值 按位或 并将结果保存到另一寄存器中, 如:
orr x0, x0, #0x1 将寄存器 x0 和常量 1 按位或后赋值到x0
- str : 将寄存器中的值写入到内存中,如:
str x0, [x0, x8] ; 将寄存器 x0 保存到栈内存 [x0 + x8]
- ldr: 将内存中的值读取到寄存器中,如:
ldr x0, [x1, x2] 将寄存器 x1 和寄存器 x2 的值相加作为地址,去该内存地址的值存储到 x0
2.2 sil验证
通过下面命令,生成 sil 文件
swiftc -emit-sil ViewController.swift > ./ViewController.sil
//需要将 UIKit 相关内容编译成 sil 时使用
swiftc -emit-sil -target x86_64-apple-ios15.0-simulator -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) ViewController.swift > ./ViewController.sil
获取到 sil 的文件,可以看到文件最后有个 sil_vtable 的结构,里面存放了类的实例方法,由名称来看是应该是虚表,进一步验证了之前关于类的实例方法的猜想
3、结构体的方法调用
struct Swagger {
func test() {
print("test")
}
func test1() {
print("test1")
}
func test2() {
print("test2")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = Swagger()
t.test()
t.test1()
t.test2()
}
}
在 Swift 中,调用一个结构体的方法是直接拿到函数的地址直接调用,包括初始化方法。Swift 是一门静态语言,许多东西在运行的时候就可以确定了,所以才可以直接拿到函数的地址进行调用,这个调用的形式称作静态派发
4、其他函数调度方式
4.1 extension 函数调用
可以看到,无论是 class
或者是struct
在extension 中的的方法都是通过静态派发的方式
4.2 继承自NSObject的类
用同样的方式验证得到继承自NSObject
类的sil_vtable
和纯swift
的类一样,类的实例方法会存储在Vtable
中,通过Vtable
函数表派发,extension
的方法不会存储,通过静态派发调用
4.3 方法调度总结
4.4 关键字对派发方式的影响
class classSwagger{
final func finalSwag() { }
static func staticSwag() { }
dynamic func dynamicSwag() { }
@objc func objcSwag() { }
@objc dynamic func objcDynamicSwag() { }
}
final
添加了final
关键字的函数无法被重写,使用静态派发,不会在vtable
中出现,且 对 objc 运行时不可⻅
static
static
方法不会存在vTable
中,也是通过静态派发dynamic
为非 objc 类和值类型的函数赋予动态性,修饰的方法会存在vtable中,通过函数表派发调用@objc
该关键字可以将 Swift 函数暴露给 objc 运行时,与 OC 交互,修饰的方法会存在vtable中,通过函数表派发调用@objc + dynamic
@objc + dynamic 就会变成消息派发的方式-也就是 OC 中的消息机制
5、内联函数
内联函数是一种编译器优化技术,它通过使用方法的内容替换直接调用该方法,从而优化性能
swift 内联
- Swift 中的内联函数是默认行为,我们无需执行任何操作,Swift 编译器可能会自动内联函数作为优化
- always – 将确保始终内联函数。通过在函数前添加 @inline(__always) 来实现此行为
- never – 将确保永远不会内联函数。这可以通过在函数前添加 @inline(never) 来实现
- 如果函数很⻓并且想避免增加代码段大小,请使用@inline(never)(使用@inline(never))
Xcode 设置
private 的优化操作
如果对象只在声明的文件中可⻅,可以用 private 或 fileprivate 进行修饰。编译器会对 private 或 fileprivate 对象进行检查,确保没有其他继承关系的情形下,自动打上 final 标记,进而使得对象获得静态派发的特性(fileprivate: 只允许在定义的源文件中访问,private: 定义的声明中访问)
今天的文章Swift 类与结构体(下)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19591.html