Swift -- 2.类与结构体(下)
一.异变方法
1.值类型方法
Swift中的class和struct都能定义方法。但是有一点区别的是默认情况下,值类型的属性不能被自身的实例方法修改。
struct Point {
var x = 0.0, y = 0.0
func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
代码会报错,因为在moveBy方法内修改x或y值相当于修改其本身(self)
要想使得值类型在实例方法里修改其属性,需要加上关键字mutating
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
2.分析mutating关键字
这里给Point添加了一个普通方法test
struct Point {
var x = 0.0, y = 0.0
func test(){
let tmp = self.x
}
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
生成SIL文档
// Point.test()
sil hidden @$s4main5PointV4testyyF : $@convention(method) (Point) -> () {
// %0 "self" // users: %2, %1
bb0(%0 : $Point):
debug_value %0 : $Point, let, name "self", argno 1 // id: %1
%2 = struct_extract %0 : $Point, #Point.x // user: %3
debug_value %2 : $Double, let, name "tmp" // id: %3
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$s4main5PointV4testyyF'
- 函数默认参数类型为
Point
,也就是self
。(OC中函数默认的参数,self、_cmd
) -
debug_value %0 : $Point, let, name "self", argno 1
,相当于let self = Point
// Point.moveBy(x:y:)
sil hidden @$s4main5PointV6moveBy1x1yySd_SdtF : $@convention(method) (Double, Double, @inout Point) -> () {
// %0 "deltaX" // users: %10, %3
// %1 "deltaY" // users: %20, %4
// %2 "self" // users: %16, %6, %5
bb0(%0 : $Double, %1 : $Double, %2 : $*Point):
//声明一个deltaX赋值给%0
debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3
//声明一个deltaY赋值给%1
debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4
debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
...
} // end sil function '$s4main5PointV6moveBy1x1yySd_SdtF'
- 加上
mutating
默认传入的参数变为@inout Point
-
debug_value_addr %2 : $*Point, var, name "self", argno 3
,相当于var self = &Point
,
SIL文档对@inout
的解释
An @inout parameter is indirect. The address must be of an initialized object.(当前参数类型是间接的,传递的是已经初始化过的地址)
异变方法的本质:对于变异方法,传入的self
被标记为inout
参数。无论在mutating
方法内部发生什么,都会影响外部依赖类型的一切。也就是说,mutating标记的方法,可以修改本身的值
输入输出参数
:如果我们想函数能够修改一个形式参数的值,并且希望这些改变在函数结束之后依然生效,那么久需要将形式参数定义为输入输出形式参数
。在形式参数定义开始的时候在前边添加一个inout
关键字可以定义一个输入输出形式参数
var age = 10
//函数的形式参数都是let类型的
func modifyAge(_ age: inout Int) {
age += 1
}
modifyAge(&age) //传入的是age地址
print(age) // 11
3.代码案例来理解mutating
struct Point {
var x = 0.0, y = 0.0
func test(){
let tmp = self.x
}
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var p = Point()
//相当于test方法传入的p, let self = Point
let x1 = p
//相当于moveBy方法传入的&p, var self = &Point
var x2 = withUnsafePointer(to: &p){return $0}
var x3 = p // 相当于值拷贝,2个独立的结构体实例
p.x = 30.0
//x2和x3的x值会发生变化吗?
//x2的会发生变化,x3的值不会发生变化
//x2和p是2个一模一样的实例,指向同一一块内存空间
print(x2.pointee.x) // 30.0
print(x3.x) // 0
对应SIL
debug_value_addr %0 : $*Int, var, name "age", argno 1 // id: %1
相当于 var age = &age
二.方法调度
对于Objective-C
来说通过objc_msgSend
进行方法调度
那么,在Swift中的方法调度是怎么样的呢
1.汇编分析
class LGTeacher{
func teach(){
print("teach")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var t = LGTeacher()
t.teach()
}
}
创建项目,使用真机调试,查看汇编来分析方法调度
在t.teach()
打上断点,进入汇编调试模式
我们知道在汇编代码中blr
代表着有返回值的跳转,将断点移到blr x8
。表示跳转到x8寄存器中的地址
进来后发现,这里面就是方法teach
的调用
此时,虽然找到了teach
的函数调用,那么这和方法调度有什么关系呢?我们继续往下
在Teacher中再添加2个teach
方法
class LGTeacher{
func teach(){
print("teach")
}
func teach1(){
print("teach1")
}
func teach2(){
print("teach2")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
var t = LGTeacher()
t.teach()
t.teach1()
t.teach2()
}
}
teach函数的调用过程:找到metadata
,确定函数地址(metadata + 偏移量)
,执行函数。
teach、teach1、teach2相差的就是函数指针的大小,在内存地址上是连续的内存空间
一些汇编指令
bl:(branch)跳转到某地址(无返回)
blr:跳转到某地址(有返回)
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中
位与符号是&,真值表达式为: 1&1=1,1&0=0,0&1=0,0&0=0
orr:将某一寄存器的值和另一寄存器的值 按位或 并将结果保存到另一寄存器中:
orr x0, x0, #0x1 将寄存器x0的值和常量1按位或后保存到寄存器x0中
位或符号是|,真值表达式为: 1|1=1,1|0=1,0|1=1,0|0=0
str:将寄存器中的值写入到内存中:
str x0, [x0, x8] 将寄存器x0的值保存到栈内存[x0 + x8]处
ldr:将内存中的值读取到寄存器中:
ldr x0, [x1, x2] 将寄存器x1和寄存器x2的值相加作为地址,取该内存地址的值放入寄存器x0中
cbz: 和 0 比较,如果结果为零就转移(只能跳到后面的指令)
cbnz: 和非 0 比较,如果结果非零就转移(只能跳到后面的指令)
cmp: 比较指令
ret: 子程序(函数调用)返回指令,返回地址已默认保存在寄存器 lr (x30) 中
2.SIL上查看sil_vtable
sil_vtable LGTeacher {
#LGTeacher.teach: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC5teachyyF // LGTeacher.teach()
#LGTeacher.teach1: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach1yyF // LGTeacher.teach1()
#LGTeacher.teach2: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach2yyF // LGTeacher.teach2()
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s14ViewController9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s14ViewController9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
3.源码找到vtable
在类与结构体(上)总结的Metadata
中有一个重要的字段typeDescriptor
,类型描述
//TargetClassMetadata中
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
//进入TargetClassDescriptor,发现它有一个别名,全局搜索ClassDescriptor查找在哪里构建这个结构体
using ClassDescriptor = TargetClassDescriptor<InProcess>;
//进入GenMeta.cpp,找到ClassContentDescriptorBuilder
void layout() {
super::layout();
addVTable();
addOverrideTable();
addObjCResilientClassStubInfo();
maybeAddCanonicalMetadataPrespecializations();
}
//super.layout
void layout() {
asImpl().computeIdentity();
super::layout();
asImpl().addName();
asImpl().addAccessFunction();
asImpl().addReflectionFieldDescriptor();
asImpl().addLayoutInfo();
asImpl().addGenericSignature();
asImpl().maybeAddResilientSuperclass();
asImpl().maybeAddMetadataInitialization();
}
//addVTable()
void addVTable() {
LLVM_DEBUG(
llvm::dbgs() << "VTable entries for " << getType()->getName() << ":\n";
for (auto entry : VTableEntries) {
llvm::dbgs() << " ";
entry.print(llvm::dbgs());
llvm::dbgs() << '\n';
}
);
// Only emit a method lookup function if the class is resilient
// and has a non-empty vtable, as well as no elided methods.
if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal)
&& (HasNonoverriddenMethods || !VTableEntries.empty()))
IGM.emitMethodLookupFunction(getType());
//VTableEntries可以理解为数组
if (VTableEntries.empty())
return;
//计算了一个偏移量
auto offset = MetadataLayout->hasResilientSuperclass()
? MetadataLayout->getRelativeVTableOffset()
: MetadataLayout->getStaticVTableOffset();
//B就是TargetClassDescriptor
//添加偏移量
B.addInt32(offset / IGM.getPointerSize());
//添加vtable的size
B.addInt32(VTableEntries.size());
//遍历数组,添加函数指针
for (auto fn : VTableEntries)
emitMethodDescriptor(fn);
}
//通过TargetClassDescriptor、TargetTypeContextDescriptor、TargetContextDescriptor的源码总结出的TargetClassDescriptor
//TargetClassDescriptor继承自TargetTypeContextDescriptor
//TargetTypeContextDescriptor继承自TargetContextDescriptor
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
//对应上面添加的size =》B.addInt32(VTableEntries.size());
var size: UInt32
//V-Table
}
4.Mach-O验证TargetClassDescriptor
Mach-O:其实是Mach Object
文件格式的缩写,是mac以及iOS
上可执行文件的格式,类似于Windows上的PE(Portable Executable)
,Linux上的elf格式(Executable and Linking Format)
。常见的.o
,.a
,.dylib Framework
,dylib.dsym
Mach-O文件格式:
Mach-O
- 首先是文件头,表明该文件是Mach-O格式,制定目标架构,还有一些其他的文件属性信息,文件头信息影响后续文件的文件结构
- Load commands是一张包含很多内容的表。内容包括区域的位置、符号表、动态表等。
name | info |
---|---|
LC_SEGMENT_64 | 将文件中(32位或64位)的段映射到进程地 址空间中 |
LC_DYLD_INFO_ONLY | 动态链接相关信息 |
LC_SYMTAB | 符号地址 |
LC_DYSYMTAB | 动态符号表地址 |
LC_LOAD_DYLINKER | dyld加载 |
LC_UUID | 文件的UUID |
LC_VERSION_MIN_MACOSX | 支持最低的操作系统版本 |
LC_SOURCE_VERSION | 源代码版本 |
LC_MAIN | 设置程序主线程的入口地址和栈大小 |
LC_LOAD_DYLIB | 依赖库的路径,包含三方库 |
LC_FUNCTION_STARTS | 函数起始地址表 |
LC_CODE_SIGNATURE | 代码签名 |
- Data区主要就是负责代码和数据记录的。Mach-O是以Segment这种结构来组织数据的,一个Segment可以包含0个或多个Section。根据Segment是映射的哪一个Load Command,Segment中section就可以被解读为是代码、常量或者一些其他的数据类型。在装载在内存中时,也是根据Segment做内存映射的
-
__PAGEZERO
主要是将低地址占用,防止用户访问。这里的VM Size
就是Mach-o
的基地址 - 程序在运行时,会加上
ASLR(地址空间布局随机化)
,来保证程序运行的安全
- OC的类存放在
Section64(_DATA_CONST,__objc_classlist)
- Swift的类、结构体、枚举存放在
Section64(__TEXT,__swift5_types)
,每4字节作为一个区分,具体存放的是TargetClassDescriptor
地址信息。
在这里第一个4字节就是我们今天要验证的LGTeacher
的TargetClassDescriptor
这里的第一个4字节,小端模式,读取为0xFFFFFBA8
0xFFFFFBA8 + 0xBBCC = 0x10000B774(Descriptor在Mach-O文件的内存地址)
减去虚拟内存基地址(VM Address): 0x100000000
得出地址为:0xB774(Descriptor在Data数据区的内存地址)
找到0xB774,熟悉Mach-O的应该知道,这肯定在_TEXT_const
中
- 这个地方就是
Descriptor
的内容,也就是起始位置
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
//对应上面添加的size =》B.addInt32(VTableEntries.size());
var size: UInt32
//V-Table
}
这个是我们之前查看源码总结的TargetClassDescriptor数据结构
,根据该数据结构,在0xB774进行偏移得到vtable
的内存地址,偏移12个4字节
- size为0x00000004
- 0xB7A8就是
teach
在Mach-O
中的信息
验证这个0xFFFFC04400000010
就是我们的teach
信息
1.使用image list
找到ASLR(地址空间布局随机性,Address Space Layout Randomization)
,也就是程序在启动的时候随机偏移了一个地址,也称为程序的基地址。
(lldb) image list
[ 0] 12C1648A-0E28-3BB8-A1F1-CCD13EDCBE38 0x0000000104a68000 /Users/zt/Library/Developer/Xcode/DerivedData/projectTest-bmemtiaawffbzbcslwyavvzqqwmh/Build/Products/Debug-iphoneos/projectTest.app/projectTest
-
0x0000000104a68000
就是程序运行的基地址
-
0x0000000104a68000
+0xB7A8(偏移量)
=0x104A737A8
就是函数在内存的地址
此时0x104A737A8
指向的就是Mach-O中的0xFFFFC04400000010
,也就是上图中的vtable起始位置
3.源码查看swift函数在内存的数据结构
struct TargetMethodDescriptor {
/// Flags describing the method.
MethodDescriptorFlags Flags; //标识方法的类型,4字节
/// The method implementation.
TargetRelativeDirectPointer<Runtime, void> Impl; //imp的指针,存储的offset
// TODO: add method types or anything else needed for reflection.
};
此时算出的0x104DA37A8就是指向的teach(TargetMethodDescriptor)
偏移Flags(4字节),加上offset(0xFFFFC044)
0x104A737A8 + 0x4 + 0xFFFFC044 = 0x204A6F7F0
减去虚拟基地址0x100000000(VM Address)
得到0x104A6F7F0
4.汇编验证函数地址
在执行到第一个x8
寄存器时,读取x8
的值
(lldb) register read x8
x8 = 0x0000000104a6f7f0 projectTest`projectTest.LGTeacher.teach() -> () at ViewController.swift:11
5.至此证明了TargetClassDescriptor数据结构
Swift代码
import UIKit
class LGTeacher{
func teach(){
print("teach")
}
func teach1(){
print("teach1")
}
func teach2(){
print("teach2")
}
}
class ViewController: UIViewController {
//ASLR:地址空间分布随机性(offset)
//使用image list获取程序基地址(ASLR offset + __PAGEZERO) 0x0000000104a68000
//0x0000000104a68000 + 0xB7A8 = 0x104A737A8
//加上偏移0x4,加上offset, 减去程序的基地址0x100000000得到方法函数的内存地址
//0x104A737A8 + 0x4 + 0xFFFFC044 - 0x100000000 = 0x104A6F7F0
/*
(lldb) register read x8
x8 = 0x0000000104a6f7f0 projectTest`projectTest.LGTeacher.teach() -> () at ViewController.swift:11
*/
override func viewDidLoad() {
let t = LGTeacher()
t.teach()
t.teach1()
t.teach2()
}
}
三.影响函数派发方式
1.结构体派发
struct LGTeacher{
func teach(){
print("teach")
}
func teach1(){
print("teach1")
}
func teach2(){
print("teach2")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
let t = LGTeacher()
t.teach()
t.teach1()
t.teach2()
}
}
-
可以看到,这里的3个方法在汇编代码中是
直接派发
的。代码编译后,函数地址就确定了。 -
为什么?
值类型没有继承关系,因此也就不需要记录我们的函数,编译器就优化为了静态调用,类似于静态函数 -
源码查看
Struct
是否存在vtable
进入GenMeta.cpp,找到StructContextDescriptorBuilder(与ClassContextDescriptorBuilder对应)
//laout
void layout() {
super::layout();
maybeAddCanonicalMetadataPrespecializations();
}
void addLayoutInfo() {
// uint32_t NumFields;
B.addInt32(getNumFields(getType()));
// uint32_t FieldOffsetVectorOffset;
B.addInt32(FieldVectorOffset / IGM.getPointerSize());
}
并没有vtable的相关方法
2.使用extension添加的方法
1.使用extension给Struct
添加一个方法
extension LGTeacher {
func teach3() {
print("teach3")
}
}
Struct
通过extension
添加的方法是静态派发
2.使用extension给Class
添加一个方法
类通过extension
添加的方法也是静态派发
3.理解为什么类使用extension
不写入函数表里面
这里Teacher有2个函数teach
和teach1
,LGPartTeacher有4个函数(继承了2个函数teach
、teach1
和自身的teach2
、teach3
)。假如这2个类在A.swift
文件中,在B.swift
文件中给LGTeacher
添加teach4
函数。当程序在编译A.swift
时,编译了这2个类,此时vtable
就生成了。此时程序再编译到B.swift
时,LGTeacher
中有一个extension
,如果要在vtable
添加的话,就应该在teach1
下追加一个teach4
。对于LGPartTeacher
来说就应该加载teach1
下面,但是LGPartTeacher
已经确定了,再在中间插入一个MethodDescriptor
,需要移动之前的teach2
和teach3
指针,还需要有一个下标来记录位移的index来保证extension
添加的方法能够找到位置插入,此番操作代价太昂贵,因此编译器将extension
添加的方法优化为静态调用
。不会影响原有的vtable结构
3.方法调度的总结
类型 | 调用方式 | extension |
---|---|---|
值类型 | 静态派发 | 静态派发 |
Swift类 | 函数表派发 | 静态派发 |
NSObject类 | 函数表派发 | 静态派发 |
4.影响函数派发方式
-
static
、private
、fileprivate
添加后,会变成静态派发 -
final
添加了final关键字的函数无法被重写,使用静态派发,不会在vtable
中出现,且对objc运行时
不可见
//实际开发过程中属性、方法、类不需要被重载的时候,使用final
class LGTeacher{
//写上了final关键字,静态派发
final func teach(){
print("teach")
}
}
let t = LGTeacher()
t.teach()
-> 0x102bf7bd8 <+56>: bl 0x102bf771c ; projectTest.LGTeacher.teach() -> () at ViewController.swift:12
-
dynamic
函数均可添加dynamic
关键字,为非objc类和值类型的函数赋予动态性
,但派发方式还是函数表派发
。也就是可以动态的替换,使用了该字段才能使用编译器字段@_dynamicReplecement(for:xx)
来替换方法的imp。注意,@_dynamicReplecement
只能在extension
里使用
class LGTeacher{
//依然还是函数表的调度
dynamic func teach(){
print("teach")
}
}
extension LGTeacher {
//将teach的imp改为了tech3
//具体可以查看SIL代码。实际上是保留了2个代码的imp,一个orignal_entry,一个forward_to_replace
@_dynamicReplacement(for: teach)
func teach3() {
print("teach3")
}
}
let t = LGTeacher()
t.teach()
执行结果为teach3
-
@objc
该关键字可以将Swift函数暴露给Objc运行时
,依旧是函数表派发。 -
@objc + dynamic
消息派发的方式,objc_msgSend
。能够使用Runtime Api
来动态修改。
class LGTeacher {
//@objc,将方法暴露给OC,函数表派发
//dynamic,方法具有动态性,函数表派发
//二者结合过后变为,消息派发(objc_msgSend)
//加上@objc + dynamic变为消息调度的机制,objc_msgSend
//此时就可以使用Runtime Api,method-swizzling、...
@objc dynamic func teach(){
print("teach")
}
}
此时此刻,对于这个teach
函数OC来说,能调用得到吗?
答案肯定是调用不到的,因为它是一个纯粹的Swift类
。我们也可以在Xcode
中查看xxxx-swift.h
(Swift暴露给OC的声明文件)中是否有这个类。
进入发现没有LGTeacher
类的相关信息。但是可以在Swift代码中对@objc dynamic
标记的函数使用Runtime Api
。
比如当前类使用method-swizzling
class LGTeacher {
//加上@objc变为消息调度的机制,objc_msgSend
//此时就可以使用Runtime Api,method-swizzling、...
@objc dynamic func teach(){
print("teach")
}
@objc dynamic func teach1(){
print("teach1")
}
func teach2(){
print("teach2")
}
}
extension LGTeacher {
//将teach的imp改为了tech3
//具体可以查看SIL代码。实际上是保留了2个代码的imp,一个orignal_entry,一个forward_to_replace
@_dynamicReplacement(for: teach)
func teach3() {
print("teach3")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
let teachSel = #selector(LGTeacher.teach)
let teach1Sel = #selector(LGTeacher.teach1)
// let teachImp = class_getMethodImplementation(LGTeacher.self, teachSel)
// let teach1Imp = class_getMethodImplementation(LGTeacher.self, teach1Sel)
//
// //替换方法
// class_replaceMethod(LGTeacher.self, teachSel, teach1Imp!, nil)
// class_replaceMethod(LGTeacher.self, teach1Sel, teachImp!, nil)
let teachMethod = class_getInstanceMethod(LGTeacher.self, teachSel)
let teach1Method = class_getInstanceMethod(LGTeacher.self, teach1Sel)
method_exchangeImplementations(teachMethod!, teach1Method!)
let t = LGTeacher()
t.teach()
t.teach1()
// 执行结果
// teach1
// teach3
}
}
如果想让OC使用到这个类,必须让这个类继承自NSObject
此时在xxx-swift.h中就有了相关的声明
SWIFT_CLASS("_TtC11projectTest9LGTeacher")
@interface LGTeacher : NSObject
- (void)teach;
- (void)teach1;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
四.函数内联
函数内联
是一种编译器优化技术,它通过使用方法的内容替换直接调用该方法,从而优化性能。
- 将确保有时内联函数。这是默认行为,我们无需执行任何操作,Swift编译器可能会自动内联函数作为优化
-
always
将确保始终内联函数。通过在函数前添加@inline(__always)
来实现此行为 -
never
将确保永远不会内联函数。这可以通过在函数前添加@inline(never)
来实现 - 如果函数很长并且想避免增加代码段大小,请使用
@inline(never)
//始终内联函数
@inline(__always) func test() {
print("test")
}
//永远不内联函数
@inline(never) func test1() {
print("test1")
}
工程中优化选项
1.分析代码在不同优化等级下汇编代码
class ViewController: UIViewController {
override func viewDidLoad() {
let a = sum(a: 1, b: 2)
}
}
func sum(a:Int, b:Int) -> Int {
return a + b
}
- 默认
Not Optimization
Not Optimization
0x102ce3a9c <+428>: mov w8, #0x1 将1复制到w8寄存器
0x102ce3aa4 <+436>: mov w8, #0x2 将2复制到w8寄存器
0x102ce3aac <+444>: bl 0x102ce3af0 ;
projectTest.sum(a: Swift.Int, b: Swift.Int) -> Swift.Int at ViewController.swift:90 执行sum函数
- 修改
Optimization Level
为Optimize for Speed
此时细心的你就会发现如果还是将断点断在let a = sum(a: 1, b: 2)
下一行的话,此时不会进入断点。因为编译已经优化掉了,此时的let a = sum(a: 1, b: 2)
其实就是3。因此,加上打印语句,断点下载print
上
class ViewController: UIViewController {
override func viewDidLoad() {
let a = sum(a: 1, b: 2)
print(a)
}
}
func sum(a:Int, b:Int) -> Int {
return a + b
}
Optimize for speed
0x102bfd4e4 <+188>: mov w8, #0x3 //将3复制到w8寄存器中
此时,在汇编里面已经没有关于sum函数调用了。编译器直接将1+2=3计算出来了
这样的行为就称为编译器优化技术
2.private、fileprivate对函数派发的影响
- 如果对象只在声明的文件中可见,可以用
private
或fileprivate
进行修饰。编译器会对private
或fileprivate
对象进行检查,确保没有其他继承关系的情形下,自动打上final
标记,进而使得对象获得静态派发
的特性(fileprivate
只允许在定义的源文件中访问,private
定义的声明 中访问)
class LGPerson{
private var sex: Bool
private func unpdateSex(){
self.sex = !self.sex
}
init(sex innerSex: Bool) {
self.sex = innerSex
}
func test() {
self.unpdateSex()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
let t = LGPerson(sex: true)
t.test()
}
}
在函数test
中的self.unpdateSex()
下一个断点
- 此时可以发现函数
unpdateSex
静态派发了,那是否vtable
里没有这个函数了?
此时我们发现vtable里依然有unpdateSex,只是编译器针对private将函数调用方式优化了
sil_vtable LGPerson {
#LGPerson.sex!getter: (LGPerson) -> () -> Bool : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvg // LGPerson.sex.getter
#LGPerson.sex!setter: (LGPerson) -> (Bool) -> () : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvs // LGPerson.sex.setter
#LGPerson.sex!modify: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC3sex33_37ACD668159BB52851391EE68C0B8918LLSbvM // LGPerson.sex.modify
#LGPerson.unpdateSex: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC10unpdateSex33_37ACD668159BB52851391EE68C0B8918LLyyF // LGPerson.unpdateSex()
#LGPerson.init!allocator: (LGPerson.Type) -> (Bool) -> LGPerson : @$s14ViewController8LGPersonC3sexACSb_tcfC // LGPerson.__allocating_init(sex:)
#LGPerson.test: (LGPerson) -> () -> () : @$s14ViewController8LGPersonC4testyyF // LGPerson.test()
#LGPerson.deinit!deallocator: @$s14ViewController8LGPersonCfD // LGPerson.__deallocating_deinit
}
此时将函数unpdateSex
声明的private
去掉,再次运行
- 函数
unpdateSex
调度方式为函数表派发