Swift 进阶系列 - 02 类与结构体(下)
@mutating, inout 关键字
@mutating
关键字:
The mutating keyword is only required if you are changing any state contained within the struct.
mutating关键字只有在你改变结构体内部状态时才需要使用
inout
关键字:
inout means that modifying the local variable will also modify the passed-in parameter.
inout 意味着 修改局部变量也会修改对应传入的参数
以上关键字的本质都是指针传递, 所以可以直接修改对应对象的内存区域,由于是指针传递, 所以在值类型的变量上使用才是有意义的。
方法调度
class 实例方法调用:
1.找到 Metadata
2.确定函数地址(metadata + offset)
3.执行函数
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
}
SIL 文件分析可以找到类方法的函数表(V-Table), 通过Swift源码分析可以得知, V-Table可能存在于
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
}
Mach-O:Mach-O 其实是Mach Object文件格式的缩写,是 mac 以及 iOS 上可执行文件的格式,常⻅的有 .o,.a .dylib Framework,dyld ,.dsym
-
首先是文件头,表明该文件是 Mach-O 格式,指定目标架构,还有一些其他的文件属性信息,文件头信息影响后续的文件结构安排
-
Load commands是一张包含很多内容的表。内容包括区域的位置、符号表、动态符号表 等。
-
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 做内存映射的。
Swift类信息存放在swift_types section中. 存放descriptor的地址信息, 找到offset.
虚拟内存基地址在Load Commands __PageZero可以找到 VM Size.Data
根据基地址+offset 在const section中 找到descriptor 的首地址, 根据ClassDescriptor的结构,之后通过 image list 的起始模块, 找到ASLR (程序运行的起始地址),最后推算出V-Table的首地址。
通过 MethodDescriptor 找到方法的结构(flags: UInt32(4字节) + impl:(函数偏移Offset)), 最后确定函数地址(经过汇编调试可以确定函数地址是否一致)
类型 | 调度方式 | extension |
---|---|---|
值类型 | 静态派发 | 静态派发 |
类 | 函数表派发 | 静态派发 |
NSObject的子类 | 函数表派发 | 静态派发 |
- final: 添加了 final 关键字的函数无法被重写,使用静态派发,不会在 vtable 中出现,且 对 objc 运行时不可⻅。
- dynamic: 函数均可添加 dynamic 关键字,为非objc类和值类型的函数赋予动态性,但派发 方式还是函数表派发。
- @objc: 该关键字可以将Swift函数暴露给Objc运行时,依旧是函数表派发。
- @objc + dynamic: 消息派发的方式