swift 进阶之路:学习大纲
前言
参考:https://www.jianshu.com/p/4515a6798b3c
一、类的方法调度
对于结构体中的⽅法都是静态调⽤(直接调⽤),那对于类⽐的 class 中的⽅法那?我们类中声明的⽅法 是通过 V-table 来进⾏调度的。
V-Table 在 SIL 中的表示是这样的:
//声明sil vtable关键字
1 decl ::= sil-vtable
//sil vtable中包含 关键字、标识(即类名)、所有的方法
2 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
//方法中包含了声明以及函数名称
3 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na
me
这⾥我们通过⼀个简单的源⽂件来看⼀下:
class LGTeacher {
func teach()
func teach2()
func teach3()
func teach4()
@objc deinit
finit()
}
LGTeacher函数表
sil_ctable LGTeacher {
#LGTeacher.teach!1;(LGTeacher)-()-(): @main.LGTeacher.teach()→()
#LGTeacher.teach!2;(LGTeacher)-()-(): @main.LGTeacher.teach2()→()
#LGTeacher.teach!3;(LGTeacher)-()-(): @main.LGTeacher.teach3()→()
#LGTeacher.teach4!1:(LGTeacher)→()→():@main.LGTeacher.teach4()→()
#LGTeacher.init!allocator.1:(LGTeacher.Type)-()→LGTeacher:@main.LGTeacher._atlocating_init()→ main.LGTeacher
#LGTeacher.deinit!deatlocator.1:@main.LGTeacher.deatLocating_deinit
}
⾸先是 sil_vtable 的关键字,然后是 LGTeacher 表明当前是 LGTeacher class 的函数表 其次就是当前⽅法的声明对应着⽅法的名称 这张表的本质其实就类似我们理解的数组,声明在 class 内部的⽅法在不加任何关键字修饰的过程中, 连续存放在我们当前的地址空间中。 接下来我们通过断点来直观的看⼀下,⾸先我们需要明确⼏个指令:
bl:跳转到某地址
blr: 带返回的跳转指令,跳到指令后寄存器中的地址
mov: 将寄存器的值赋值到另一寄存器(mov x1, x0: 将寄存器x0的值复制到寄存器x1中)
ldr: 将内存中的值读取到寄存器中(ldr x0, [x1, x2]: 读取x1+x2地址,返回值给x0)
str: 将寄存器中的值写入内存中(str x0,[x1, x2]: 寄存器x0的值,存放在x1+x2的地址处)
举例验证:
汇编中可以看到,struct 是直接调用地址,而class的函数调用,先找到vtable,再调用函数。
二、class 的 extension方法调度
class
的extension
方法的调度,是直接调度
。不会记入vtable表中。
重写父类方法会怎样?
总结:
- 继承父类class中的方法,会被写入vtable中:
- 没有重写,直接记录父类的函数。
- 如果重写,会记录自己的函数
- 父类extension中的方法,不会写入vtable中,也不可被重写。但可被子类调用。
- 没被写入vtable的方法,都是通过地址直接调用。
1.1 final修饰
-
final
修饰的函数
或属性
,都不
会写入vtable
中,子类不可重写
,但可调用
@objc修饰
-
声明
该函数
可被OC
使用,编译后
都会生成2个函数
(原函数
和@objc函数
)。
可以发现,
仅
用@objc修饰
的函数
,本质
上是调用
了没被@objc
修饰的原函数
- 如果仅仅是支持
#selector()
的调用,直接使用@objc
即可。 - 如果需要
OC
文件中调用
这个@objc
的函数
,需要让类继承
自NSObject
。只有继承自NSObject
的类,才能被OC访问
到。
OC-Swift 桥接演示
- 创建一个
OC项目
,新建一个SwiftTest.swift
文件:![image](//upload-images.jianshu.io/upload_images/12857030-535158b2e9660eaa.png?imageMogr2/auto-orient/strip|imageView2/2/w/682)
- 可以看到
桥接文件
(swift在OC中的头文件):![image](//upload-images.jianshu.io/upload_images/12857030-39d12f80cfed972c.png?imageMogr2/auto-orient/strip|imageView2/2/w/745)
进入桥接文件,可以看到被
@objc
声明的属性
和函数
都生成了OC格式
。
(没@objc
声明的,不
会生成
)
OC
调用swift
文件,直接导入
文件OCDemo-Swift.h
,就可以调用了:![image](//upload-images.jianshu.io/upload_images/12857030-825d61a03fa09142.png?imageMogr2/auto-orient/strip|imageView2/2/w/609)
dynamic
- 将
函数
变为动态性
-
dynamic
声明的函数
,依旧是vtable
中调用
:
-
dynamic
声明的函数
,支持动态替换
_dynamicReplacement
可替换dynmic
声明的函数
_dynamicReplacement
只能在extention
中使用
-
swift
函数具备OC动态性
(函数调用
使用objc_msgSend
)
- 用
@objc
和dynamic
同时修饰函数
,就可以让swift函数
的调用
变成objc_msgSend
方式:(如果再继承NSObject
,就可以被OC文件
使用)![image](//upload-images.jianshu.io/upload_images/12857030-2c1b90970a2fdbf7.png?imageMogr2/auto-orient/strip|imageView2/2/w/689)
- 以上,就是
swift
函数调度
,以及所有修饰符
的作用
。
总结
struct
:函数直接调用
class
:写在class中
函数会被记录在vtable
中间接调用
,但写在extension
中的会被直接调用
。class
的父类
,写在extension
中的函数不可
被子类重写
,但可调用
。调用方式是直接调用
final
:修饰的函数
和属性
,不被写入vtable
中,不可
被子类重写
,但可调用
@objc
:修饰的函数
和属性
,本质会生成原函数
+@objc函数
,其中@objc函数
可被OC类
使用,如果class类
需要被OC使用
,则需要继承
自NSObject
dynamic
:修饰的函数
可具备动态性
,可在extension
中进行@_dynamicReplacement (for: XXX)
动态交换。dynamic
配合@objc
一起使用,是直接调用OC
的消息机制(objc_msgSend)
进行函数调用
。
三、指针
swift的指针分为两类,typed pointer
(指定类型指针)和raw pointer
(未知的类型的指针 - 原生指针)
-
raw pointer
:在swift中表示的是UnsafeRawPointer
-
typed pointer
,:在swift中表示的是UnsafePointer
(T是泛型)
swift指针
和OC指针
对应关系:
-
swift
中指针操作
,都是unsafe
不安全的,操作不当可能crash
。需要程序员
自己判断
。
3.1 RawPointer的使用
- 以
4个
的Int整形
数据的存储
和读取
为例:
/**
RawPointer 未指定类型(原生指针)的使用
*/
// 1\. 可变原生指针: 指定开辟32字节空间,遵循8字节对齐规则 (swift源码中可看到调用Builtin标准模块的allocRaw)
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 2\. 不同空间存放不同内容
// advanced: p前进的步长。(从p指针地址开始,需要偏移多少字节。 这个内存大小就是MemoryLayout.stride)
// storyBytes: 存储内容(of: 指定存储内容(T), as: 内容的类型(T.Type))
for i in 0..<4 {
p.advanced(by: i * 8).storeBytes(of: i+1, as: Int.self)
}
// 3\. 读取内存内容
// fromByteOffset: 从p指针地址开始,需要偏移多少字节
// as: 内容的类型(T.Type)
for i in 0..<4 {
let value = p.load(fromByteOffset: i * 8, as: UInt64.self)
print(value)
}
// 4\. 释放指针。 (allocate和deallocate是对应关系。创建了就需要手动释放)
p.deallocate()
【注意】
RawPointer
是未指定类型指针
, 可以看到我在storeBytes
中,as
传的是Int.self
。而load读取
时,使用的UInt64.self
- 这是因为
storeBytes
时,并不
会指定类型
,而仅仅是以该类型
的大小
为存储大小,进行空间
的划分
。- 所以
读取时
,我们只要按照相同大小
的类型
去读取数据
,就可以完美
的完成类型转换
。(64位系统
下,Int
和UInt64
都是16字节
大小)在带来操作
便捷性
的同时,会带来crash
风险(存储
和读取
内存单位不一致
)。所以对指针的操作
是Unsafe不安全
的。
dvanced的补充
RawPointer
(未指定类型)的指针
:dvanced
接收完整bytes偏移值
,typed pointerr
(已指定类型 )的指针
:dvanced
接收单位偏移数
案例演示:
/** RawPointer 未指定类型的指针 */ // 1\. 申请32字节空间,8字节对齐 let p1 = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8) for i in 0..<4 { // 2\. 偏移i*8插入i+1的值,占据Int类型大小 p1.advanced(by: i * 8).storeBytes(of: i+1, as: Int.self) //3\. 通过偏移拿到起始地址,读取Int类型数据 print(p1.load(fromByteOffset: i * 8, as: Int.self)) } // 4\. 释放 p1.deallocate() /** Typed Pointer 已指定类型的指针 */ // 1\. 申请4个Int空间(共32字节) let p2 = UnsafeMutablePointer
.allocate(capacity: 4) for i in 0..<4 { // 2\. 每个(Int)空间initialize初始化,并赋值I+1 p2.advanced(by: i).initialize(to: I+1) // 3\. 直接下标打印(因为固定了类型,元素大小一样) print(p2[I]) // print((p2 + i).pointee) // 也可通过单位地址偏移,读取pointee内容进行打印 } // 4\. deinitialize释放指定元素个数的空间 p2.deinitialize(count: 2) // 5\. 释放指针 p2.deallocate()
3.2 Type Pointer的使用
指针
地址
、内容
的读取
和映射
:【方法一】 通过
Swift
提供的API
进行操作:
withUnsafePointer
: 不可更改原值
withUnsafeMutablePointer
: 可更改原值
var age = 10
print("------1-------")
// 1.通过Swift提供的API,读取指针的地址
let p = withUnsafePointer(to: &age) { $0 }
// pointee存放了指针的所有属性
print(p.pointee) // 打印结果:10
print("------2-------")
// 2\. withUnsafePointer 返回值是UnsafePointer不可修改的值。
// 我们不可以修改$0.pointee的值,但可以在闭包中对指针内容包装,可转换为任意类型的内容进行输出。
var b = withUnsafePointer(to: &age) { "age:\($0.pointee) 被我改成String了" }
print(b) // 打印结果: age:10 被我改成String了
print(type(of: b)) // 打印结果: String
print("------3-------")
// 3\. 如果想要在闭包内修改原值,就必须使用
print("修改前age:\(age)") // 打印结果:修改前age:10
withUnsafeMutablePointer(to: &age) { $0.pointee += 10 }
print("修改后age:\(age)") // 打印结果:修改前age:20
- 【方法二】 通过
指针内存
,进行操作
:
var age = 10
// 1.使用UnsafeMutablePointer可变的指定Int类型为指针进行操作,读取一个单位内存大小
// capacity: 容量个数,表示1个Int类型大小,为8字节。
let p = UnsafeMutablePointer.allocate(capacity: 1)
print("初始化前的p:\(p.pointee)") //打印内容: 初始化前的p:0
// 2\. 通过【值拷贝】复制age指针内容,初始化当前的指针p
p.initialize(to: age)
print("初始化后的p:\(p.pointee)") //打印内容:初始化后的p:10
p.pointee = 666
print("p值修改后的p:\(p.pointee)") // 打印内容:p值修改后的p:666
print("p值修改后的age:\(age) " ) // 打印内容:p值修改后的age:10 【没有改变,说明上面是值拷贝】
// 3\. 释放指针空间
p.deinitialize(count: 1)
// 4\. 释放指针
p.deallocate()
所有对
指针
的操作
,都需要手动管理内存
:
- 开辟的
内存
都需要手动释放
- 用完的
指针
都要释放
实战案例:
- 重写
swift对象结构
和类结构
,读取
系统对象
的指针空间
,强转
为我们的对象
和类
,进行内容分析。// 自定义对象默认结构(后面的自定义属性就不取了) struct HeapObject { var metadata: UnsafeRawPointer var strongRef: UInt32 var unownedRef: UInt32 } // 自定义swift类结构 struct Swift_class { var kind: UnsafeRawPointer var superClass: UnsafeRawPointer var cacheData1: UnsafeRawPointer // 因为系统数据结构是CacheData[2],所以用2个来接 var cacheData2: UnsafeRawPointer var data: UnsafeRawPointer var flags: UInt32 var instanceAddressOffset: UInt32 var instanceSize: UInt32 var flinstanceAlignMask: UInt16 var reserved: UInt16 var classSize: UInt32 var classAddressOffset: UInt32 var description: UnsafeRawPointer } class HTTeacher { var age = 18 } // 实例变量 var t = HTTeacher() // 1\. 将t变量转换为任意类型的指针(不对引用计数进行操作) // Unmanaged: 不托管的. 有passUnretained(引用计数不加1) 和 passRetained(应用计数+1) 两种。 // toOpaque: 将类型对象转换为指针(不安全的) let p = Unmanaged.passUnretained(t as AnyObject).toOpaque() // 2\. 将p指针绑定为HeapObject类型的指针 // bindMemory: 绑定内存为HeapObject类型,空间大小为1个HeapObject的stride步>长大小 let heapObject = p.bindMemory(to: HeapObject.self, capacity: 1) // 3\. 将heapObject内部的metadata指针绑定为Swift_class类型指针 let metadata = heapObject.pointee.metadata.bindMemory(to: Swift_class.self, capacity: 1) // 【注意】只有UnsafeRawPointer未指定类型的指针,才可以使用bindMemory,转为为绑定指定类型的指针。 // p指针通过toOpaque转为RawPointer,而heapObject的metadata指针,在struct结构中就定义为RawPointer。所以都可使用bindMemory // 聪明的你,应该感受到,如果我直接在HeapObject结构中,就将metadata指定为UnsafePointer
,就完全不需要第3步动态绑定了。 print(metadata.pointee) /** 打印结果: Swift_class(kind: 0x00000001000081d0, // 类型 superClass: 0x00007fff91f91060, cacheData1: 0x00007fff6a3cb140, cacheData2: 0x0000002000000000, data: 0x0000000104049732, flags: 2, instanceAddressOffset: 0, instanceSize: 24, // 实例大小 基础的16字节 + age 8字节 = 24 flinstanceAlignMask: 7, reserved: 0, classSize: 136, classAddressOffset: 16, description: 0x0000000100003bec) */ 总结
- 使用
Unmanaged
的toOpaque
方法,将实例变量
指针转换为UnsafeRawPointer
未指定类型的指针。- 调用
bindMemory
函数,将UnsafeRawPointer
指针绑定为UnsafePointer< HeapObject >
绑定类型为HeapObject
的指针- 同样调用
bindMemory
函数,将heapObject
的metadata
的UnsafeRawPointer
指针绑定为UnsafePointer
指针。- 此时可打印
metadata.pointee
查看内部结构。
- 此案例主要目的,是演示
类型强转
,类似OC的__bridge
。业务中
如果类型不一致
,但我们完全确定
可以是某种类型
时,可
进行类型
的转换
。还是得
提醒
一下,任何对指针的操作
,都是不安全
的,程序员全责
3.3 元组指针类型的转换
var tuple = (10, 20)
func testPointer(_ p: UnsafePointer) {
print("地址:\(p) 内容:\(p.pointee)")
print("end")
}
withUnsafePointer(to: &tuple) { (t: UnsafePointer<(Int, Int)>) in
let a = UnsafeRawPointer(t).bindMemory(to: Int.self, capacity: 1)
testPointer(a) // 打印内容: 地址:0x00000001000081d8 内容:10
let b = UnsafeRawPointer(t).assumingMemoryBound(to: Int.self)
testPointer(b) // 打印内容: 地址:0x00000001000081d8 内容:10
//(testPoiinter函数中,并不知道入参是元组,只是打印首地址内容:第一个元素10)
print("--- 元组的打印 ---")
print(t.pointee) // 打印内容:(10, 20)
print(t.pointee.0) // 打印内容:10
print(t.pointee.1) // 打印内容:10
}
bindMemory
、assumingMemoryBound
、withMemoryRebound
的区别
bindMemory
:更改
内存绑定
的类型
(之前没绑定
,就首次绑定
,如果绑定了
,重新绑定
为新类型
)assumingMemoryBound
:假定
内存绑定
(告诉编译器
,不用检查
,它就是我说的类型)withMemoryRebound
:临时
更改内存
绑定类型
withMemoryRebound
的使用![image](//upload-images.jianshu.io/upload_images/12857030-961fa9a3f055f134.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200)
3.4 如何拿到结构体属性
的指针
由于结构体
的属性
是直接存储
的值内容
。所以读取时:
-
非引用类型
:直接读到值内容
-
引用类型
:读取到对象指针地址
但结构体属性
既然存在
于内存
中,无论
是否是引用类型
,一定有指针地址
。我们可以通过结构体对象
进行指针偏移
,拿到属性地址
。
class HTPerson {
var age = 1
}
struct HeapObject {
var perosn = HTPerson()
var strongRef = 10
var unownedRef = 20
}
var t = HeapObject()
func testPointer(_ p: UnsafePointer) {
print("地址:\(p) 内容:\(p.pointee)")
}
// Q: 问题: 如何拿到HeapObject实例的stongRef属性指针
//指针偏移(从headpObject开始偏移)
withUnsafePointer(to: &t) {
// offset: 指定属性名称
let personRef = UnsafeRawPointer($0) + MemoryLayout.offset(of: \HeapObject.perosn)!
testPointer(personRef.assumingMemoryBound(to: Int.self)) // 打印结果: 地址:0x0000000100008380 内容:4354357216
/**
lldb打印: x/4gx 4354357216
0x1038a37e0: 0x0000000100008248 0x0000000000000002
0x1038a37f0: 0x0000000000000001(这个就是age) 0x0002000000000000
*/
let strongRef = UnsafeRawPointer($0) + MemoryLayout.offset(of: \HeapObject.strongRef)!
testPointer(strongRef.assumingMemoryBound(to: Int.self)) // 打印结果: 地址:0x00000001000081f8 内容:10
let unownedRef = UnsafeRawPointer($0) + MemoryLayout.offset(of: \HeapObject.unownedRef)!
testPointer(unownedRef.assumingMemoryBound(to: Int.self)) // 打印结果: 地址:0x0000000100008200 内容:20
}