mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: clang 插件开发
This commit is contained in:
646
Chapter1 - iOS/1.116.md
Normal file
646
Chapter1 - iOS/1.116.md
Normal file
@@ -0,0 +1,646 @@
|
||||
# Swift 类底层剖析
|
||||
|
||||
## 类的内存结构
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var age: Int = 0
|
||||
}
|
||||
|
||||
class Student: Person {
|
||||
var score: Int = 0
|
||||
}
|
||||
|
||||
class Worker: Student {
|
||||
var salary: Int = 0
|
||||
}
|
||||
|
||||
let person = Person()
|
||||
person.age = 28
|
||||
print(Mems.size(ofRef: person))
|
||||
print(Mems.memStr(ofRef: person))
|
||||
|
||||
32
|
||||
0x000000010000c400 0x0000000000000003
|
||||
0x000000000000001c 0x0000000000000000
|
||||
|
||||
let student = Student()
|
||||
student.score = 100
|
||||
print(Mems.size(ofRef: student))
|
||||
print(Mems.memStr(ofRef: student))
|
||||
32
|
||||
0x000000010000c4b0 0x0000000000000003
|
||||
0x000000000000001c 0x0000000000000064
|
||||
|
||||
let worker = Worker()
|
||||
worker.salary = 1000
|
||||
print(Mems.size(ofRef: worker))
|
||||
print(Mems.memStr(ofRef: worker))
|
||||
48
|
||||
0x000000010000c580 0x0000000000000003
|
||||
0x000000000000001c 0x0000000000000064 0x00000000000003e8 0x00007ff8501c0938
|
||||
```
|
||||
|
||||
- 内存对齐都是16 Byte 的整数倍
|
||||
- 一个类内存中,至少占16字节的内存。前8位是类信息、其次的8位是引用计数信息,最后跟属性内存
|
||||
- 由于类存在继承,所以子类中,前16字节存储类信息和引用计数信息,其次是属性内存,存在继承的话,前面的属性是父类的属性,后面才是自己的属性。
|
||||
|
||||
所以:
|
||||
|
||||
- Person 类的内存: 8 Byte 的类信息 + 8 Byte 引用计数信息 + 8 Byte Int Age 属性 = 24 Byte,由于需要16的倍数,所以是32 Byte
|
||||
- Student 类的内存: 8 Byte 的类信息 + 8 Byte 引用计数信息 + 8 Byte Int Age 属性 + 8 Byte 的 Int Score 属性 = 32 Byte,由于需要16的倍数,所以是32 Byte
|
||||
- Worker 类的内存: 8 Byte 的类信息 + 8 Byte 引用计数信息 + 8 Byte Int Age 属性 + 8 Byte 的 Int Score 属性 + 8 Byte 的 Int Salary 属性 = 40 Byte,由于需要16的倍数,所以是 48 Byte
|
||||
|
||||
|
||||
|
||||
## 继承
|
||||
|
||||
值类型(枚举、结构体)不支持继承,只有类支持继承
|
||||
|
||||
没有父类的类,称为基类。Swift 并不像 OC、Java 那样规定:任何类最终都要继承自某个基类(OC 的 NSObject)。
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
class Person {}
|
||||
class Student: Person {}
|
||||
print(class_getSuperclass(Student.self)!) // Person
|
||||
print(class_getSuperclass(Person.self)!) // _TtCs12_SwiftObject
|
||||
```
|
||||
|
||||
丛输出可以看出 Swift 还存在一个隐藏基类:`Swift._SwiftObject`,可查看 [Swift 源码](https://github.com/apple/swift/blob/main/stdlib/public/runtime/SwiftObject.h)
|
||||
|
||||
|
||||
|
||||
## 方法
|
||||
|
||||
结构体和枚举是值类型,默认情况下,值类型的属性是不能被自身的实例方法修改。
|
||||
|
||||
如果想在方法内修改,需要在 `func` 前加 `mutating` 才可以
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x: Double = 0.0
|
||||
var y: Double = 0.0
|
||||
func moveBy(_ delatX: Double, _ delatY: Double) {
|
||||
self.x += delatX
|
||||
self.y += delatY
|
||||
}
|
||||
}
|
||||
var point = Point()
|
||||
point.moveBy(0.2, 0.2)
|
||||
// compiler error
|
||||
Left side of mutating operator isn't mutable: 'self' is immutable
|
||||
```
|
||||
|
||||
改进
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x: Double = 0.0
|
||||
var y: Double = 0.0
|
||||
mutating func moveBy(_ delatX: Double, _ delatY: Double) {
|
||||
self.x += delatX
|
||||
self.y += delatY
|
||||
}
|
||||
}
|
||||
var point = Point()
|
||||
point.moveBy(0.2, 0.4)
|
||||
print(point.x, point.y)
|
||||
// 0.2 0.4
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 重写方法
|
||||
|
||||
`override`
|
||||
|
||||
被 class 修饰的类型方法、下标,允许被子类重写
|
||||
|
||||
被 static 修饰的类型方法、下标,不允许被子类重写
|
||||
|
||||
```swift
|
||||
class Animal {
|
||||
static var innerValue:Int = 0
|
||||
class func speak() {
|
||||
print("Animal speak")
|
||||
}
|
||||
|
||||
class subscript(index: Int) -> Int {
|
||||
set {
|
||||
innerValue = newValue
|
||||
}
|
||||
get {
|
||||
innerValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Dog: Animal {
|
||||
override class func speak() {
|
||||
super.speak()
|
||||
print("dog is bark")
|
||||
}
|
||||
override class subscript(index: Int) -> Int {
|
||||
set {
|
||||
innerValue = newValue
|
||||
}
|
||||
get {
|
||||
innerValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animal.speak() // Animal speak
|
||||
Animal[5] = 3
|
||||
print(Animal[5]) // 3
|
||||
Dog.speak() // Animal speak dog is bark
|
||||
```
|
||||
|
||||
但如果将 `Animal` 方法的 `class` 改为 `static`,就无法 `override` 了
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftCannotOverrideStaticMethod.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
## 重写属性
|
||||
|
||||
- 子类不可以将父类的属性改写为存储属性
|
||||
- 子类可以将父类的属性(存储属性、计算属性)重写为计算属性
|
||||
- 只能重写 var 属性,不能重写 let 属性
|
||||
- 重写时,属性名、类型要一致
|
||||
- 子类重写后的属性权限(读写),不能小于父类属性的权限
|
||||
- 如果父类属性是只读的,子类重写后的属性要么是只读的,要么是可读可写的
|
||||
- 如果父类的属性是可读可写的,子类重写后的属性也必须是可读可写的
|
||||
|
||||
|
||||
|
||||
## 重写类型属性
|
||||
|
||||
- 被 class 修饰的计算类型属性,可以被子类重写
|
||||
- 被 static 修饰的类型属性(存储、计算),不可以被子类重写
|
||||
- 可以在子类中为父类属性(除了只读的计算属性、let 属性)增加属性观察器
|
||||
|
||||
```swift
|
||||
class Shape {
|
||||
var radius: Int = 1 {
|
||||
willSet {
|
||||
print("Shape will set radius", newValue)
|
||||
}
|
||||
didSet {
|
||||
print("Shape did set radius", oldValue, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
class Circle: Shape {
|
||||
override var radius: Int {
|
||||
willSet {
|
||||
print("Cirle will set radius", newValue)
|
||||
}
|
||||
didSet {
|
||||
print("Circle did set radius", oldValue, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
var circle = Circle()
|
||||
circle.radius = 2
|
||||
// console
|
||||
Cirle will set radius 2
|
||||
Shape will set radius 2
|
||||
Shape did set radius 1 2
|
||||
Circle did set radius 1 2
|
||||
```
|
||||
|
||||
可以看到输出类似 Node 的洋葱模型,willset 从外到里,didset 从里到外。
|
||||
|
||||
|
||||
|
||||
## final
|
||||
|
||||
- 被 final 修饰的方法、属性、下标是禁止被重写的
|
||||
|
||||
- 被 final 修饰的类,禁止被继承
|
||||
|
||||
|
||||
|
||||
## <span id="target-anchor">多态的实现原理</span>
|
||||
|
||||
OC: Runtime
|
||||
|
||||
C++:虚表
|
||||
|
||||
Swift:没有 Runtime,所以多态的实现类似 C++
|
||||
|
||||
```swift
|
||||
class Animal {
|
||||
func speak () {
|
||||
print("Animal speak")
|
||||
}
|
||||
func eat () {
|
||||
print("Animal eat")
|
||||
}
|
||||
func sleep () {
|
||||
print("Animal sleep")
|
||||
}
|
||||
}
|
||||
|
||||
class Dog: Animal {
|
||||
override func speak() {
|
||||
print("Dog speak")
|
||||
}
|
||||
override func eat() {
|
||||
print("Dog eat")
|
||||
}
|
||||
func run () {
|
||||
print("Dog run")
|
||||
}
|
||||
}
|
||||
|
||||
var animal = Animal()
|
||||
animal.speak()
|
||||
animal.eat()
|
||||
animal.sleep()
|
||||
|
||||
animal = Dog()
|
||||
animal.speak()
|
||||
animal.eat()
|
||||
animal.sleep()
|
||||
// console
|
||||
Animal speak
|
||||
Animal eat
|
||||
Animal sleep
|
||||
Dog speak
|
||||
Dog eat
|
||||
Animal sleep
|
||||
```
|
||||
|
||||
在 `animal.speak()` 处加断点,可以看到
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo1.png" style="zoom:25%">
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo3.png" style="zoom:25%">
|
||||
|
||||
解释:
|
||||
|
||||
- 汇编84行 `movq 0x9356(%rip), %r13 ` 是将全局变量 `animal` 的地址赋值给 `r13`
|
||||
- 汇编90行 `movq (%r13), %rax` 将 `r13` 处取出内存的前8个字节,赋值给 `rax`
|
||||
- 汇编91行 `callq *0x50(%rax)` ,也就是计算出 `rax + 0x50` 的地址,然后取出8 Byte 出来,也就是 `Dog.speak` 然后调用
|
||||
- 汇编107行 `callq *0x58(%rax)` ,也就是计算出 `rax + 0x508` 的地址,然后取出8 Byte 出来,也就是 `Dog.eat` 然后调用
|
||||
- 汇编123行 `callq *0x60(%rax)` ,也就是计算出 `rax + 0x60` 的地址,然后取出8 Byte 出来,也就是 `Animal.sleep` 然后调用
|
||||
|
||||
|
||||
|
||||
画了张图,也就是说 `rax` 中存放了 Dog 对象内存中的前8个字节,也就是下图的最右侧
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
核心是上面的内存布局图。结合汇编就知道多态是如何实现的。
|
||||
|
||||
总结: **虚函数表**(vtable)是一种用于实现动态多态性的机制,通常用于面向对象的编程语言中(C++ 也是一样)。在 Swift 中,虚函数表用于存储类或协议中方法的地址,以便在运行时进行动态分派。
|
||||
|
||||
在 Swift 中,虚函数表的作用是为每个类或协议创建一个表,其中包含了对应方法的地址。当调用对象的方法时,运行时系统会根据对象的实际类型查找对应的虚函数表,然后调用表中存储的方法地址,从而触发特定的实现。
|
||||
|
||||
虚函数表在 Swift 中的作用是实现动态分派,使得在运行时根据对象的实际类型确定调用的具体实现。这为 Swift 中的多态性提供了基础,允许相同的方法名称根据对象的类型触发不同的实现,从而实现灵活的对象行为。
|
||||
|
||||
|
||||
|
||||
## 类的类型信息存储在哪
|
||||
|
||||
说明:同一个类的不同对象,它的类信息是一样的。也就是说不通的对象指针,所指向的类信息内存是同一块。
|
||||
|
||||
```swift
|
||||
var dog1 = Dog()
|
||||
var dog2 = Dog()
|
||||
```
|
||||
|
||||
存储在全局区。可以利用 MachOView 去查看。
|
||||
|
||||
|
||||
|
||||
## 初始化器
|
||||
|
||||
### require
|
||||
|
||||
- 用 required 修饰的指定初始化器,表明其所有的子类都必须实现该初始化器(通过继承或者重写来实现)
|
||||
- 如果子类重写了 required 初始化器,也必须加上 required,不用加 override
|
||||
|
||||
|
||||
|
||||
## 可失败初始化器
|
||||
|
||||
类、结构体、枚举都可以使用 `init?` 定义可失败初始化器,也可以用 `init!` 来定义可失败初始化器。区别下面会讲
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var name: String
|
||||
init?(_ name: String) {
|
||||
if name.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
var person1 = Person("")
|
||||
print(person1) // nil
|
||||
var person2 = Person("FantasticLBP")
|
||||
print(person2) // Optional(SwiftDemo.Person)
|
||||
print(person2!) // SwiftDemo.Person
|
||||
```
|
||||
|
||||
这种设计系统中也存在,比如 Int 的可失败初始化器:`@inlinable public init?(_ description: String)`
|
||||
|
||||
```swift
|
||||
var num = Int("12e2")
|
||||
print(num) // nil
|
||||
num = Int("12")
|
||||
print(num) // Optional(12)
|
||||
```
|
||||
|
||||
注意点:
|
||||
|
||||
1. 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器。因为在外部调用的时候,不知道到底是使用哪个初始化方法。编译器会报错 `Invalid redeclaration of 'init(_:)'`
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftCanFailedInit.png" style="zoom:25%">
|
||||
|
||||
2. 可以用 `init!` 来定义隐式解包的可失败初始化器
|
||||
|
||||
3. 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包。如果直接调用会报错 `A non-failable initializer cannot delegate to failable initializer 'init(_:)' written with 'init?'`
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var name: String
|
||||
init?(_ name: String) {
|
||||
if name.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
convenience init() {
|
||||
self.init("")! // 极端 case,设计不合理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
非可失败初始化器也可以调用可失败初始化器的隐式解包。
|
||||
|
||||
```swift
|
||||
class Person2 {
|
||||
var name: String
|
||||
init!(_ name: String) {
|
||||
if name.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
convenience init() {
|
||||
self.init("")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftCanFailedInit2.png" style="zoom:25%">
|
||||
|
||||
且前面的写法比较危险,假设第一个 `init?` 返回 `nil`,第二个 `convenience init()` 去对 nil 强制解包,则会 crash
|
||||
|
||||
4. 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来不行
|
||||
|
||||
5. 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var name: String
|
||||
init?(_ name: String) {
|
||||
if name.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
convenience init?() {
|
||||
self.init("")
|
||||
print("我是后面的代码1")
|
||||
print("我是后面的代码2")
|
||||
}
|
||||
}
|
||||
|
||||
var person1 = Person()
|
||||
print(person1)
|
||||
```
|
||||
|
||||
`init` 初始化失败,后面的 `我是后面的代码1` 均不会执行
|
||||
|
||||
### 可失败初始化器设计哲学
|
||||
|
||||
- 安全性优先:Swift 注重安全性,可失败初始化器的设计使得对象的初始化过程更加可靠和安全。通过返回一个可选值来表示初始化成功或失败,可以避免在初始化失败时产生不确定的对象状态
|
||||
- 错误处理:可失败初始化器与 Swift 的错误处理机制结合使用,使得在初始化失败时能够更好地捕获和处理错误。这种设计哲学强调了对异常情况的处理和错误信息的传递。
|
||||
- **灵活性**:可失败初始化器提供了一种灵活的初始化机制,允许开发者更加精确地控制对象的初始化过程。这种设计哲学使得对象初始化更加灵活和可定制。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 可选链
|
||||
|
||||
```swift
|
||||
var dict:[String: (Int, Int) -> Int] = [
|
||||
"sum": (+),
|
||||
"minus": (-),
|
||||
"multiple": (*),
|
||||
"divide": (/)
|
||||
]
|
||||
print(dict["sum"]) // Optional((Function))
|
||||
var result = dict["divide"]?(40, 20) // 2
|
||||
print(result!)
|
||||
```
|
||||
|
||||
- 如果可选项为 nil,调用方法、下标、属性失败,结果为 nil
|
||||
- 如果可选项不为 nil,调用方法、下标、属性成功,结果会被包装为可选项
|
||||
- 如果结果本来是可选项,则不会进行再次包装
|
||||
- 如果链中任何一个节点为 nil,那么整个链就会调用失败。`var weight = person?.dog?.weight // Int?`
|
||||
- 多个 `?` 可以链接在一起 `var weight = person?.dog?.weight`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## X.self , X.Type, AnyClass
|
||||
|
||||
- `X.self` 是一个元类型(metadata)的指针,metadata 存放着类型相关信息
|
||||
- `X.self` 属于 `X.type` 类型
|
||||
|
||||
|
||||
|
||||
通过汇编探究下背后细节
|
||||
|
||||
```swift
|
||||
class Person { }
|
||||
var person: Person = Person()
|
||||
var personType: Person.Type = Person.self
|
||||
```
|
||||
|
||||
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassMetaDataTypeDemo1.png" style="zoom:25%">
|
||||
|
||||
在第二行代码下断点,可以看到关键的汇编是第8行和第12行:
|
||||
|
||||
- 第14行可以看到 `rip + 0x89ae = 0x10000396a + 0x89ae = 0x10000C318 `,明显是一个堆地址空间,也就是全局变量 `person`
|
||||
- 第15行可以看到 `rip + 0x89af = 0x100003971 + 0x89af = 0x10000C320 `,明显是一个堆地址空间,也就是全局变量 `personType`
|
||||
- 顺着关键代码找上去,看看 `rax`、`rcx` 的值是哪来的
|
||||
|
||||
- 第8行调用函数后可以看到 Xcode 的说明,获取 `metadata`,函数返回值保存到 `rax`,LLDB 打印出为 `0x000000010000c248`
|
||||
|
||||
- 第11行初始化堆内存后,将地址保存到寄存器 `rax`,LLDB 打印出地址为 `0x0000600000004010`,然后查看 `0x0000600000004010` 对应的对象信息,可以看到内存的前8个字节的值,就是上面得到的 `metadata` 对象的地址值
|
||||
|
||||
- `metadata` 结构类似下图右侧
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
`X.self` 和 `type(of:x)` 效果等价
|
||||
|
||||
```swift
|
||||
class Person { }
|
||||
var person: Person = Person()
|
||||
print(Person.self == type(of: person)) // true
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 元类型的应用
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
required init() {}
|
||||
}
|
||||
class Worker: Person {}
|
||||
class Student: Person {}
|
||||
func createInstance(_ items: [Person.Type]) -> [Person] {
|
||||
var people:[Person] = Array<Person>()
|
||||
for item in items {
|
||||
people.append(item.init())
|
||||
}
|
||||
return people
|
||||
}
|
||||
|
||||
let student = Student()
|
||||
let studentType = type(of: student)
|
||||
let workerType = Worker.self
|
||||
var people: Array<Person> = createInstance([studentType, workerType])
|
||||
print(people) // [SwiftDemo.Student, SwiftDemo.Worker]
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Self
|
||||
|
||||
Self 一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以当作参数类型)
|
||||
|
||||
```swift
|
||||
protocol Runable {
|
||||
func copy() -> Self
|
||||
}
|
||||
|
||||
class Person: Runable {
|
||||
required init() {}
|
||||
func copy() -> Self {
|
||||
type(of: self).init()
|
||||
}
|
||||
}
|
||||
|
||||
class Student: Person { }
|
||||
|
||||
var person = Person()
|
||||
print(person.copy()) // Person
|
||||
var student = Student()
|
||||
print(student.copy()) // Student
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## OC/Swift 运行时
|
||||
|
||||
### 消息派发方式
|
||||
|
||||
消息派发方式有3种
|
||||
|
||||
#### 直接派发(Direct Dispatch)
|
||||
|
||||
会将整个方法的地址,直接硬编码到函数调用的地方。直接派发是最快的,不止是因为需要调用的指令集会更少,并且编译器还能够有很大的优化空间,例如函数内联等,直接派发也被称为静态调用
|
||||
|
||||
然而,对于编程来水,直接调用也是最大的局限,而且因为缺乏动态性,所以没有办法支持继承和多态等特性。
|
||||
|
||||
|
||||
|
||||
#### 函数表派发(Table Dispatch)
|
||||
|
||||
函数表派发是编译型语言实现动态行为最常见的方式。寒暑表使用了一个数组来存储类生命的每一个函数的指针。大部分语言把整个称为“Virtual table”(虚函数表、虚表,c++),Swift 里称为 “witness table”。每一个类都会维护一个函数表,里面记录着类所有的函数,如果父类函数被 override 的话,表里面只会保存被 overrride 后的函数。一个子类新添加的函数都会被插入到这个数组的最后,运行时会根据这一个表去决定实际需要被调用的函数。
|
||||
|
||||
就像上面[多态实现的原理](#target-anchor)这里讲到的一样
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
查表是一种简单、易实现、性能可预知的方式。然而,这种派发方式比起直接派发来说,还是慢了一点(从字节码的角度来看,多了两次读和一次跳转。由此带来了性能损耗)。另一个慢的原因在于编译器可能会由于函数内执行的任务,导致无法优化(如果函数带有副作用的话)
|
||||
|
||||
这种基于数组的实现,缺陷在于函数表无法拓展。子类会在虚函数表的最后插入新函数,没有位置可以让 extension 安全地插入函数。
|
||||
|
||||
|
||||
|
||||
#### 消息机制派发(Message Dispatch)
|
||||
|
||||
消息机制是调用函数最动态的方式,也是 Cocoa 的基石,催生了 KVO、UIAppearance、CoreData 等,这种运作方式的关键在于开发者可以在运行时改变函数的行为。不止可以通过 swizzling 来改变,甚至可以用 isa-swizzling 修改对象的继承关系,可以在面向对象的基础上实现自定义派发。
|
||||
|
||||
|
||||
|
||||
### OC 运行时
|
||||
|
||||
主要体现在
|
||||
- 动态类型(dynamic typing)
|
||||
- 动态绑定(dynamic binding)
|
||||
- 动态装载(dynamic loading)
|
||||
|
||||
|
||||
|
||||
### Swift 运行时
|
||||
- 纯 Swift 类的函数调用已经不再是 Objective-C 的运行时发消息,而是类似 c++ 的虚表 vtable,在编译时就确定了调用哪个函数,所以没办法通过 runtime 获取方法、属性
|
||||
- 而 Swift 为了兼容 Objective-C,凡是继承自 NSObject 的类都会保留其动态性,所以能够通过 runtime 拿到方法。老版本的 swift(如2.2)是编译期隐式的自动帮你加上了 `@objc`,而4.0以后版本的 swift 编译期去掉了隐式特性,必须显示声明
|
||||
- 不管是 Swift 类,还是继承自 NSObject 的类,只要在属性和方法前面加 `@objc` 关键字,就可以使用 runtime
|
||||
|
||||
|
||||
|
||||
| | 原始定义 | 拓展 |
|
||||
| -------------------- | ---------- | ---------- |
|
||||
| 值类型 | 直接派发 | 直接派发 |
|
||||
| 协议 | 函数表派发 | 直接派发 |
|
||||
| 类 | 函数表派发 | 直接派发 |
|
||||
| 继承自 NSObject 的类 | 函数表派发 | 函数表派发 |
|
||||
|
||||
|
||||
|
||||
- 值类型总是会使用直接派发,简单易懂
|
||||
- 协议和类的 extension 都会使用直接派发
|
||||
- NSObject 的 extention 会使用消息机制进行派发
|
||||
- NSObject 声明作用域的函数都会使函数表进行派发
|
||||
- 协议里声明的,并且带有默认实现的函数会使用函数表进行派发
|
||||
|
||||
|
||||
|
||||
修饰符
|
||||
|
||||
| final | 直接派发 |
|
||||
| ---------------- | ---------------------- |
|
||||
| dynaminc | 消息机制派发 |
|
||||
| @objc & @nonobjc | 改变在 oc 里的可见性 |
|
||||
| @inline | 告诉编译器可以直接派发 |
|
||||
|
||||
|
||||
|
||||
有个特殊的组合 final 和 @objc。在标记为 final 的同时,也可以使用 @objc 来让函数可以使用消息机制派发。这么做的结果就是,调用函数的时候会使用直接派发,但也会在 Objective-C 的运行时里注册对应的 selector,函数可以响应 `perform(selector:)` 以及别的 Objective-C 特性,但在直接调用时,又可以有直接派发的性能。
|
||||
|
||||
Reference in New Issue
Block a user