mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
update: 动态库、静态库的编译链接细节
This commit is contained in:
@@ -42,7 +42,7 @@ print(Mems.memStr(ofRef: worker))
|
||||
```
|
||||
|
||||
- 内存对齐都是16 Byte 的整数倍
|
||||
- 一个类内存中,至少占16字节的内存。前8位是类信息、其次的8位是引用计数信息,最后跟属性内存
|
||||
- 一个类内存中,至少占16字节的内存。前8位是类信息、其次的8位是引用计数信息,接着是属性内存区域
|
||||
- 由于类存在继承,所以子类中,前16字节存储类信息和引用计数信息,其次是属性内存,存在继承的话,前面的属性是父类的属性,后面才是自己的属性。
|
||||
|
||||
所以:
|
||||
@@ -159,7 +159,13 @@ 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%">
|
||||
<img src="./../assets/SwiftCannotOverrideStaticMethod.png" style="zoom:25%">
|
||||
|
||||
|
||||
<img src="./../assets/SwiftCannotOverrideStaticMethod2.png" style="zoom:25%">
|
||||
|
||||
- 如果父类的方法是被 class 修饰的,子类继承后重写时,可以将 class 改为 static。
|
||||
- 虽然子类可以将父类方法的 class 改为 static。但影响的是当前子类的子类,无法再重写方法了。
|
||||
|
||||
|
||||
|
||||
@@ -222,15 +228,27 @@ Circle did set radius 1 2
|
||||
- 被 final 修饰的类,禁止被继承
|
||||
|
||||
|
||||
## Swift 协议(Protocol)中声明的属性必须使用 var 关键字
|
||||
|
||||
协议的核心目标:定义“能力”而非“实现”
|
||||
协议是描述类型应该具备什么能力的抽象蓝图,而不是具体实现。
|
||||
属性在协议中本质上定义的是对外的访问接口(读、写),而不是存储方式(常量或变量)。
|
||||
因此,**协议中的属性声明必须明确其访问权限({ get } 或 { get set }),而 var 是唯一能表达这种动态性的关键字**。
|
||||
|
||||
- 协议中的属性用 var:统一表示“访问接口”,支持动态约束({ get } 或 { get set })。
|
||||
- 遵循类型可用 let 或 var:只要满足协议的访问权限要求即可。
|
||||
- let 无法用于协议:因其无法表达可写性,违背协议动态描述能力的初衷。
|
||||
|
||||
|
||||
|
||||
|
||||
## <span id="target-anchor">多态的实现原理</span>
|
||||
|
||||
OC: Runtime
|
||||
|
||||
C++:虚表
|
||||
|
||||
Swift:没有 Runtime,所以多态的实现类似 C++
|
||||
- OC: Runtime
|
||||
- C++:虚函数表
|
||||
- Swift:没有 Runtime,所以多态的实现类似 C++
|
||||
|
||||
来个 Demo
|
||||
```swift
|
||||
class Animal {
|
||||
func speak () {
|
||||
@@ -276,9 +294,9 @@ Animal sleep
|
||||
|
||||
在 `animal.speak()` 处加断点,可以看到
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo1.png" style="zoom:25%">
|
||||
<img src="./../assets/SwiftClassPointerDemo1.png" style="zoom:25%">
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo3.png" style="zoom:25%">
|
||||
<img src="./../assets/SwiftClassPointerDemo3.png" style="zoom:25%">
|
||||
|
||||
解释:
|
||||
|
||||
@@ -292,20 +310,43 @@ Animal sleep
|
||||
|
||||
画了张图,也就是说 `rax` 中存放了 Dog 对象内存中的前8个字节,也就是下图的最右侧
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
|
||||
<img src="./../assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
核心是上面的内存布局图。结合汇编就知道多态是如何实现的。
|
||||
|
||||
1. Swift 多态的实现原理
|
||||
|
||||
Swift 的多态通过 虚函数表(vtable) 实现,这是一种 编译时确定的动态派发机制。其核心逻辑是:
|
||||
|
||||
- 每个类类型在编译时会生成一个 虚函数表,表中存储了类的方法实现指针
|
||||
- 子类继承父类时,会复制父类的虚函数表,并替换重写方法的指针为自己的实现
|
||||
- 在运行时,通过对象的 类型元数据指针 找到对应的虚函数表,从而调用正确的方法
|
||||
|
||||
动态派发与静态派发的区别:
|
||||
- 动态派发:通过虚函数表实现(例如普通类方法),允许子类重写。
|
||||
- 静态派发:编译时直接绑定方法地址(例如 final 方法、static 方法、结构体和枚举的方法),性能更高
|
||||
|
||||
2. 虚函数表(vtable)的作用
|
||||
|
||||
虚函数表的核心作用是为 动态派发 提供支持:
|
||||
- 方法重写:子类通过覆盖虚函数表中的方法指针,实现多态。
|
||||
- 运行时方法查找:对象调用方法时,通过虚函数表找到实际的方法实现。
|
||||
- 类型安全性:保证方法调用的正确性,即使对象被向上转型(例如 父类引用 = 子类对象)。
|
||||
|
||||
|
||||
总结: **虚函数表**(vtable)是一种用于实现动态多态性的机制,通常用于面向对象的编程语言中(C++ 也是一样)。在 Swift 中,虚函数表用于存储类或协议中方法的地址,以便在运行时进行动态分派。
|
||||
|
||||
在 Swift 中,虚函数表的作用是为每个类或协议创建一个表,其中包含了对应方法的地址。当调用对象的方法时,运行时系统会根据对象的实际类型查找对应的虚函数表,然后调用表中存储的方法地址,从而触发特定的实现。
|
||||
|
||||
虚函数表在 Swift 中的作用是实现动态分派,使得在运行时根据对象的实际类型确定调用的具体实现。这为 Swift 中的多态性提供了基础,允许相同的方法名称根据对象的类型触发不同的实现,从而实现灵活的对象行为。
|
||||
|
||||
最小内存占用:一个没有属性的类对象至少占用 16 字节(类型元数据指针 8 字节 + 引用计数 8 字节)。
|
||||
属性存储:属性从第 17 字节开始存储
|
||||
引用计数细节:
|
||||
- 默认情况下,引用计数直接存储在对象头部。
|
||||
- 当引用计数溢出时,Swift 会使用 Side Table 扩展存储,此时对象头部的引用计数字段会指向 Side Table。
|
||||
|
||||
|
||||
## 类的类型信息存储在哪
|
||||
@@ -323,6 +364,15 @@ var dog2 = Dog()
|
||||
|
||||
## 初始化器
|
||||
|
||||
### 初始化器可以继承
|
||||
- convenience 便捷初始化器只可以横向调用,不可以纵向调用(比如子类继承父类后,子类重写指定初始化器的时候,必须加 override 且子类中只能调用父类的指定初始化器,不能调用便捷初始化器)
|
||||
- 便捷初始化器是不能被子类调用的
|
||||
|
||||
|
||||
### 自动继承
|
||||
- 如果子类没有自定义任何指定初始化器,则会自动继承父类所有的指定初始化器
|
||||
|
||||
|
||||
### require
|
||||
|
||||
- 用 required 修饰的指定初始化器,表明其所有的子类都必须实现该初始化器(通过继承或者重写来实现)
|
||||
@@ -330,7 +380,7 @@ var dog2 = Dog()
|
||||
|
||||
|
||||
|
||||
## 可失败初始化器
|
||||
### 可失败初始化器
|
||||
|
||||
类、结构体、枚举都可以使用 `init?` 定义可失败初始化器,也可以用 `init!` 来定义可失败初始化器。区别下面会讲
|
||||
|
||||
@@ -365,7 +415,7 @@ 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%">
|
||||
<img src="./../assets/SwiftCanFailedInit.png" style="zoom:25%">
|
||||
|
||||
2. 可以用 `init!` 来定义隐式解包的可失败初始化器
|
||||
|
||||
@@ -403,7 +453,7 @@ print(num) // Optional(12)
|
||||
}
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftCanFailedInit2.png" style="zoom:25%">
|
||||
<img src="./../assets/SwiftCanFailedInit2.png" style="zoom:25%">
|
||||
|
||||
且前面的写法比较危险,假设第一个 `init?` 返回 `nil`,第二个 `convenience init()` 去对 nil 强制解包,则会 crash
|
||||
|
||||
@@ -441,7 +491,85 @@ print(num) // Optional(12)
|
||||
|
||||
|
||||
|
||||
### OC alloc init,为什么 Swift 只需要 init?
|
||||
|
||||
1. 语言设计哲学的分歧
|
||||
|
||||
OC 显示控制与动态性。OC 是 C 的超集,继承了对底层内存管理的直接控制。`alloc` 和 `init` 的分离体现了**职责分离**原则:
|
||||
|
||||
- **`alloc`**:类方法(`+alloc`),负责**内存分配**(计算对象大小、向系统申请内存空间,返回一个“空白”实例)。
|
||||
- **`init`**:实例方法(`-init`),负责**状态初始化**(设置属性默认值、建立对象依赖关系等)。
|
||||
- 这种分离允许开发者灵活干预内存分配(例如自定义 `+allocWithZone:`)或初始化过程(例如工厂方法 `+new`)。
|
||||
|
||||
Swift 简洁性与安全性
|
||||
|
||||
- Swift 作为现代语言,追求代码简洁和安全性。`Person()` 的语法**隐藏了内存分配细节**,开发者只需关注初始化逻辑。编译器会自动插入内存分配代码(类似 `__allocating_init`)并调用初始化方法。类似 `let person = Person.__allocating_init()`
|
||||
- Swift 强制在初始化完成前为所有存储属性赋值,并通过两段式初始化(Phase 1: 分配内存并设置默认值;Phase 2: 自定义初始化)避免未定义状态
|
||||
|
||||
2. 编译器与运行时的工作
|
||||
|
||||
OC:运行时开放性
|
||||
|
||||
Objective-C 的 `+alloc` 方法由运行时动态处理。开发者可以重写 `+alloc` 或 `+allocWithZone:` 实现自定义内存分配策略(例如对象池、单例)。为了实现这种灵活性,更需要显式调用 alloc
|
||||
|
||||
```objective-c
|
||||
// 自定义 alloc 方法
|
||||
+ (instancetype)alloc {
|
||||
if (单例条件) {
|
||||
return sharedInstance;
|
||||
}
|
||||
return [super alloc];
|
||||
}
|
||||
```
|
||||
|
||||
Swift: 编译时的静态优化
|
||||
|
||||
- 内存分配的编译时确定:Swift 的对象大小和内存布局在编译时即可确定(值类型更是完全静态)。编译器直接生成内存分配指令,无需运行时动态计算。
|
||||
- 初始化器的静态派发:Swift 的初始化方法通过静态派发(或虚表派发)调用,无需 Objective-C 的消息转发开销。编译器能安全地合并内存分配和初始化步骤。
|
||||
|
||||
为什么 Swift 可以省略 `alloc`?
|
||||
|
||||
1. **编译器自动化**:内存分配由编译器隐式插入代码处理,无需开发者参与。
|
||||
2. **类型安全性**:严格的初始化规则确保对象在初始化完成后处于合法状态。
|
||||
3. **现代语法设计**:隐藏底层细节,提升代码可读性和编写效率。
|
||||
4. **静态优化**:编译时确定对象内存布局,无需运行时动态分配逻辑。
|
||||
|
||||
而 Objective-C 保留 `alloc` 和 `init` 的分离,既是对历史的兼容,也为需要精细控制内存或动态行为的场景保留了灵活性。
|
||||
|
||||
|
||||
|
||||
### deinit
|
||||
|
||||
deinit 也叫反初始化器,类似于 C++ 的析构函数、OC 中的 dealloc 方法
|
||||
|
||||
当类的实例对象被释放内存时,就会调用实例对象的 deinit 方法
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
deinit {
|
||||
print("Person deinit")
|
||||
}
|
||||
}
|
||||
|
||||
class Student: Person {
|
||||
deinit {
|
||||
super.deinit() // Deinitializers cannot be accessed
|
||||
print("Student deinit")
|
||||
}
|
||||
}
|
||||
|
||||
func test() {
|
||||
let st = Student()
|
||||
}
|
||||
test()
|
||||
```
|
||||
|
||||
上述代码编译报错:Deinitializers cannot be accessed
|
||||
|
||||
deinit 的基本规则:
|
||||
|
||||
- **不可继承性**:`deinit` 本身不会被继承。每个类必须定义自己的 `deinit` 方法(显式或隐式)。
|
||||
- **自动链式调用**:无论子类是否重写 `deinit`,父类的 `deinit` 方法总会在子类析构完成后被自动调用,无需手动调用 `super.deinit()`。
|
||||
|
||||
## 可选链
|
||||
|
||||
@@ -465,6 +593,128 @@ print(result!)
|
||||
|
||||
|
||||
|
||||
## 可选项 Optional 的本质
|
||||
|
||||
可选项的本质是 **enum 类型 + 泛型**
|
||||
|
||||
```swift
|
||||
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
|
||||
|
||||
/// The absence of a value.
|
||||
///
|
||||
/// In code, the absence of a value is typically written using the `nil`
|
||||
/// literal rather than the explicit `.none` enumeration case.
|
||||
case none
|
||||
|
||||
/// The presence of a value, stored as `Wrapped`.
|
||||
case some(Wrapped)
|
||||
|
||||
/// Creates an instance that stores the given value.
|
||||
public init(_ some: Wrapped)
|
||||
}
|
||||
```
|
||||
|
||||
`var age:Intt? = 20` 是语法糖,本质是 `var age:Optional<Int> = .some(20)` 所以下面写法是一样的
|
||||
|
||||
```swift
|
||||
// 写法1
|
||||
var age1: Int? = 30
|
||||
age1 = 20
|
||||
age1 = nil
|
||||
|
||||
// 写法2
|
||||
let age2: Optional<Int> = .some(30)
|
||||
age2 = 20
|
||||
age2 = .none
|
||||
```
|
||||
|
||||
一些不合格的写法:
|
||||
|
||||
Optional 是 enum + 泛型,所以必须要设置泛型类型
|
||||
|
||||
```swift
|
||||
var age = Optional.none // Generic parameter 'Wrapped' could not be inferred
|
||||
```
|
||||
|
||||
`if let` 是专门用于 Optional 解包的语法糖。
|
||||
```swift
|
||||
var age: Int? = .none
|
||||
|
||||
if let a = age {
|
||||
print(a)
|
||||
} else {
|
||||
print("nil")
|
||||
}
|
||||
```
|
||||
等价于
|
||||
```swift
|
||||
if age != nil {
|
||||
let a = age!
|
||||
print(a)
|
||||
} else {
|
||||
print("nil")
|
||||
}
|
||||
```
|
||||
|
||||
- 只有非 nil 时,才会进入 if 分支,并将解包后的值绑定到 a
|
||||
- nil 时,直接进入 else 分支
|
||||
|
||||
`switch case` 是通用模式匹配,不针对 Optional 做特殊处理。
|
||||
```swift
|
||||
switch age {
|
||||
case let a:
|
||||
print("age is ", a)
|
||||
case nil:
|
||||
print("nil")
|
||||
}
|
||||
```
|
||||
- 第一个 case let a 会匹配所有可能的值(包括 .some(30) 和 .none,即 nil),因为 a 的类型是 Int?。
|
||||
- 一旦匹配到第一个 case,后续的 case nil 会被跳过。除了第一个之外的 case 都无法执行
|
||||
**要在 switch 中正确处理 Optional,需明确匹配 .some 和 .none,需要用 `case let a?`**
|
||||
```swift
|
||||
switch age {
|
||||
case let a?:
|
||||
print("age is ", a)
|
||||
case nil:
|
||||
print("nil")
|
||||
}
|
||||
```
|
||||
下面写法效果等价于
|
||||
```swift
|
||||
var age: Int? = .none
|
||||
age = nil
|
||||
|
||||
if let a = age {
|
||||
print(a)
|
||||
} else {
|
||||
print("nil")
|
||||
}
|
||||
|
||||
switch age {
|
||||
case let a?:
|
||||
print("age is ", a)
|
||||
case nil:
|
||||
print("nil")
|
||||
}
|
||||
|
||||
switch age {
|
||||
case let .some(a):
|
||||
print("age is ", a)
|
||||
case nil:
|
||||
print("nil")
|
||||
}
|
||||
```
|
||||
双层嵌套可选型:
|
||||
```swift
|
||||
var age1 = Optional<Int?>.some(Optional<Int>.some(30))
|
||||
var age2: Int?? = 30
|
||||
var age3: Optional<Optional> = .some(.some(30))
|
||||
var age4: Optional<Int?> = .some(30)
|
||||
print(age1!!)
|
||||
print(age2!!)
|
||||
print(age3!!)
|
||||
print(age4!!)
|
||||
```
|
||||
|
||||
|
||||
## X.self , X.Type, AnyClass
|
||||
@@ -472,8 +722,6 @@ print(result!)
|
||||
- `X.self` 是一个元类型(metadata)的指针,metadata 存放着类型相关信息
|
||||
- `X.self` 属于 `X.type` 类型
|
||||
|
||||
|
||||
|
||||
通过汇编探究下背后细节
|
||||
|
||||
```swift
|
||||
@@ -484,21 +732,25 @@ var personType: Person.Type = Person.self
|
||||
|
||||
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassMetaDataTypeDemo1.png" style="zoom:25%">
|
||||
<img src="./../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` 对象的地址值
|
||||
|
||||
- person 对象的内存布局中,前8个字节就是 personType 的地址。
|
||||
|
||||
- `metadata` 结构类似下图右侧
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
<img src="./../assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
@@ -510,9 +762,29 @@ var person: Person = Person()
|
||||
print(Person.self == type(of: person)) // true
|
||||
```
|
||||
|
||||
`AnyObject.Type` 的用法
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
|
||||
}
|
||||
class Student: Person {
|
||||
|
||||
}
|
||||
|
||||
var anyType: AnyObject.Type = Person.self
|
||||
anyType = Student.self
|
||||
|
||||
public typealias AnyClass = AnyObject.Type
|
||||
|
||||
var anyType2: AnyClass = Person.self
|
||||
anyType2 = Student.self
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 元类型的应用
|
||||
|
||||
### 元类型的应用
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
@@ -535,14 +807,95 @@ var people: Array<Person> = createInstance([studentType, workerType])
|
||||
print(people) // [SwiftDemo.Student, SwiftDemo.Worker]
|
||||
```
|
||||
|
||||
注意:为了保证子类一定有 `init(){ }` 方法,在基类中需要声明为 `required init() {}`
|
||||
|
||||
|
||||
|
||||
## Swift 继承和基类
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
class Person {
|
||||
var age:Int = 0
|
||||
}
|
||||
|
||||
class Student: Person {
|
||||
var no:Int = 0
|
||||
}
|
||||
|
||||
print(class_getInstanceSize(Student.self)) // 32
|
||||
print(class_getSuperclass(Student.self)!) // Person
|
||||
print(class_getSuperclass(Person.self)!) //_TtCs12_SwiftObject
|
||||
|
||||
```
|
||||
|
||||
分析:
|
||||
|
||||
- Student 类继承自 Person 类,类的内存布局中:
|
||||
|
||||
- isa:前8个字节是 isa 指针,指向类的元数据(AnyObject.Type),包含类型信息、方法表。 虚函数表(vtable)存储在类的元数据中,虚函数表并不直接存储在实例内存中,而是通过 isa 指向的类元数据(ClassMetadata)中。
|
||||
|
||||
调用方法时候,运行时通过 isa 找到类元数据,再从元数据中读取 vtable 地址,最终定位到具体方法实现地址
|
||||
|
||||
- 引用计数:紧接着的8个字节存储引用计数信息
|
||||
|
||||
- 紧接着是从 Person 继承来的 age 属性,占8个字节。然后是自己的 no 属性,也占8个字节。
|
||||
|
||||
- Student 类的父类是 Person 类,打印没问题
|
||||
|
||||
- Swift 类的隐式根类
|
||||
|
||||
- Swift 有个隐藏基类:`Swift._SwiftObject`
|
||||
- Person 类没有显式继承其他类,它默认会隐式继承自 Swift 的内部根类 `SwiftObject`。这个类是 Swift 运行时的基础,类似于 Objective-C 的 `NSObject`,但独立存在。蕾丝
|
||||
- `_TtCs12_SwiftObject` 是 `SwiftObject` 类在 Objective-C 运行时中的**符号化名称**(mangled name)
|
||||
- `_TtC`:Swift 类的固定前缀。
|
||||
- `s12`:模块名或类名的编码长度。
|
||||
- `SwiftObject`:实际类名
|
||||
|
||||
- 与 Objective-C 运行时的交互
|
||||
|
||||
- **`class_getSuperclass` 的局限性**
|
||||
`class_getSuperclass` 是 Objective-C 运行时函数,返回的是 Objective-C 运行时能识别的父类。由于 `SwiftObject` 是 Swift 内部类,Objective-C 运行时无法直接理解它,因此返回其符号化名称。
|
||||
- **Foundation 的影响**
|
||||
导入 `Foundation` 会引入 Objective-C 运行时,但不会改变 Swift 类的默认根类。只有显式继承 `NSObject` 的 Swift 类才会在 Objective-C 运行时中以 `NSObject` 为根类。
|
||||
|
||||
|
||||
|
||||
`Swift._SwiftObject` 的作用:
|
||||
|
||||
- **纯 Swift 类的默认父类**
|
||||
当 Swift 类不显式继承 `NSObject` 或其他类时,默认隐式继承自 `Swift._SwiftObject`。
|
||||
- **提供基础能力**
|
||||
类似于 Objective-C 的 `NSObject`,`Swift._SwiftObject` 提供了:
|
||||
- 内存管理:引用计数(通过 `swift_retain`/`swift_release`)。
|
||||
- 类型元数据:存储类的方法表、属性信息等。
|
||||
- 动态派发:支持方法重写和协议扩展。
|
||||
|
||||
通过源码查看 Swift 类的内存布局
|
||||
|
||||
```swift
|
||||
struct HeapObject {
|
||||
HeapMetadata const *metadata; // 包含 isa 和引用计数
|
||||
|
||||
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
|
||||
...
|
||||
}
|
||||
|
||||
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
|
||||
InlineRefCounts refCounts
|
||||
```
|
||||
|
||||
`HeapObject` 是 Swift 对象的基础结构,包含 `isa` 和引用计数字段
|
||||
|
||||
|
||||
|
||||
## Self
|
||||
|
||||
Self 一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以当作参数类型)
|
||||
**`Self` 是动态类型,会随着子类调用而改变**。Self 一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以当作参数类型)
|
||||
|
||||
```swift
|
||||
protocol Runable {
|
||||
init()
|
||||
func copy() -> Self
|
||||
}
|
||||
|
||||
@@ -561,6 +914,19 @@ var student = Student()
|
||||
print(student.copy()) // Student
|
||||
```
|
||||
|
||||
QA:上面的 Person 类在遵循 Runable 协议,实现 copy 方法,方法里能返回 `Person()` 吗?
|
||||
|
||||
```swift
|
||||
class Person: Runable {
|
||||
required init() {}
|
||||
func copy() -> Self {
|
||||
Person()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
答:不行。因为 Person 类可以被继承,如果 copy 方法里写死返回 Person 实例。Student 继承 Person 后,copy 方法会也会返回 Person 对象。但协议要求的是返回当前类的对象,这明显违法了协议“契约”
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -585,7 +951,7 @@ print(student.copy()) // Student
|
||||
|
||||
就像上面[多态实现的原理](#target-anchor)这里讲到的一样
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
<img src="./../assets/SwiftClassPointerDemo2.png" style="zoom:25%">
|
||||
|
||||
查表是一种简单、易实现、性能可预知的方式。然而,这种派发方式比起直接派发来说,还是慢了一点(从字节码的角度来看,多了两次读和一次跳转。由此带来了性能损耗)。另一个慢的原因在于编译器可能会由于函数内执行的任务,导致无法优化(如果函数带有副作用的话)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user