在swift底層探索 03 - 值類型、引用類型一文中解釋過值類型和引用類型的內存布局
。像這樣:
圖一
1. struct-直接調用
要想知道方法是如何調用的,我是從方法是如何保存開始探索。
從[圖一]看結構體沒有像類
那樣的繼承、metaData-ISA邏輯。而內存中也找不到方法的任何蹤跡,方法存在哪里呢?
1.1 struct方法調用
調用方法structfunc1
,然后打開匯編堆棧
圖二
- 找到了方法的地址
0x100002bd0
并且存在代碼段,但是沒法找到和結構體的關系。
machOView查看
圖三
- 這部分內存是在程序編譯鏈接的時候就已經生成好的。所以說
結構體方法在編譯鏈接階段已經確定了,并保存到符號表中
圖四
- 在
符號表中
依舊可以找到方法的指針0x100002bd0
,而且發現方法名是存在String Table
中的。所以調用前無需額外操作
1.2 struct + protocol
protocol Prot {
func protocolFunc()
}
struct structModel:Prot{
var age : Int = 18
func structfunc1(){}
func structfunc2(){}
func protocolFunc(){}
}
var str = structModel()
str.protocolFunc()
同樣打開匯編調試
- 發現
協議方法與普通方法調用是一樣的機制:直接調用
1.3 struct + extension情況
struct structModel{
var age : Int = 18
func structfunc1(){}
func structfunc2(){}
}
extension structModel{
func extensionFunc(){}
}
var str = structModel()
str.extensionFunc()
打開匯編調試
- 發現
拓展與普通方法調用是一樣的機制:直接調用
【總結】
-
結構體的方法
無需額外空間存儲,在編譯鏈接階段就已經確定,編譯器在你調用之前就已經確定了方法的指針地址,這種方法調用稱為直接調用
。
2. enum-直接調用
enum enumModel{
case `default`
func enumFunc(){}
}
enumModel.default.enumFunc()
打開匯編調試
- 與結構體相同是
直接調用
-
枚舉的方法
和結構體的方法調用機制是已完全一致的。都是直接調用
。
【總結】
-
值類型
是直接調用
3. class-方法調用(函數表調用)
3.1 普通方法
借助之前的經驗,先使用匯編來看一下調用堆棧。
class classtModel{
func classfunc1(){}
func classfunc2(){}
}
var cls = classtModel()
cls.classfunc1()
cls.classfunc2()
匯編調用堆棧
圖五
sil文件
圖六
通過圖五
,圖六
得出的結論:
-
不是直接靜態調用
,是通過對self
進行地址偏移后找到方法指針,進行調用。 - *0x50(classfunc1) -> *0x58(classfunc12兩個方法在內存里是
連續
的。
swift函數表初始化源碼
通過匯編的查看知道了方法和類本身的關系的,方法是如何存儲的呢?
- 在類初始化的時候將類中所有方法都放到
classWords
這個數組中,而且繼承 - 類的方法調用方式:
函數表調用
。
- 代碼準備
class PersonModel{
var age : Int = 18
func test(){}
func test1(){}
}
var p = PersonModel()
p.test()
p.test1()
調試驗證
- 代碼驗證,編譯并打開
匯編調試
class PersonModel{
var age : Int = 18
func test(){}
func test1(){}
}
var p = PersonModel()
p.test()
p.test1()
-
讀取寄存期
-
結論
- 可以看到
class
中的方法,是以數組的結構直接存在metaData(原類)的內存里;
swift中vtable與oc中method_list區別
oc-method_list
- 在oc中
method_list
是一個二維數組包含:普通方法(包含父類方法)數組、類別方法數組.
swift-vtable
class superClass{
func superClassfunc1(){}
}
class classtModel:superClass{
func classfunc1(){}
func classfunc2(){}
}
extension classtModel{
func extensionfunc1(){}
}
- classtModel
繼承
superClass;classtModel拓展
classtModel.
看下sil文件
- 在
class
中包含:父類方法
,本類方法
- 不包含:
拓展方法
3.2 class + protocol
protocol Prot {
func protocolFunc()
}
class classtModel:Prot{
func classfunc1(){}
func classfunc2(){}
func protocolFunc(){}
}
查看sil文件
- 協議方法出現在
vtable
中,代表和普通方法一致是:函數表調用
3.3 class + entension
class classtModel{
func classfunc1(){}
}
extension classtModel{
func extensionfunc1(){}
}
var cls = classtModel()
cls.extensionfunc1()
打開匯編調試
- 發現
entension
中的方法調用和值類型
的調用一致:直接調用
- 因為在類初始化的時候就已經完成
vtable的創建
,有繼承關系時extension沒法找到一個合理的起始位置
開發存放entension
中的方法
3.4 final、class、static關鍵字
方法前增加
final關鍵字
后,該方法不得被繼承。
class classtModel{
final func classfunc1(){}
}
var cls = classtModel()
cls.classfunc1()
打開匯編調試
- 發現在
普通方法
前加上final關鍵字
后方法調用和值類型調用方式一致
為:直接調用
.可以當做是一個靜態方法看.
注:
增加class關鍵字
、static關鍵字
后,調用方式都會變為直接調用
3.5 @objc / dynamic關鍵字
@objc是將該方法暴露給oc使用
dynamic關鍵字是將方法標記為可變方法。
以@objc為例:
class classtModel{
@objc func classfunc1(){}
dynamic func classfunc2(){}
}
查看sil文件:
- 兩個方法都出現在了
vtable
中,說明這兩個都放都是使用:函數表
3.6 @objc dynamic 組合關鍵字
@objc dynamic是將方法保留給oc且還可以動態修改
class classtModel{
@objc dynamic func classfunc2(){ }
}
var cls = classtModel()
cls.classfunc2()
- 使用
@objc dynamic
之后,相當于是和oc的方法一樣可以動態修改了。所以在調用的時候也需要使用動態消息轉發
【面試題】
protocol TestProtocol {
}
class CJLTeacher: TestProtocol{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
func test(_ value: TestProtocol){
let valueType = type(of: value)
print(valueType)
}
var t = CJLTeacher()
let t1: TestProtocol = CJLTeacher()
test(t)
test(t1)
會輸出什么?
LGTeacher teach
TestProtocol teach
- 體現了
swift的多態性
如果是這樣
protocol TestProtocol {
func teach(){
print("TestProtocol teach")
}
}
輸出又會變成:
LGTeacher teach
LGTeacher teach
【總結】
引用類型的方法調用相對復雜:直接調用
,函數表調用
,消息轉發
都會出現。
調用方式總結
值類型 | 引用類型 | |
---|---|---|
普通方法 | 直接調用 | 函數表調用 |
protocol協議 | 直接調用 | 函數表調用 |
extension拓展 | 直接調用 | 直接調用 |
final | - | 直接調用 |
繼承方法 | - | 函數表調用 |
@objc | - | 函數表調用 |
dynamic | - | 函數表調用 |
@objc dynamic | - | objc_msgSend消息轉發 |
dynamic拓展
class classtModel{
dynamic func classfunc2(){
print("classfunc2")
}
}
extension classtModel{
@_dynamicReplacement(for: classfunc2)
private func repFunc(){
print("repFunc")
}
}
var cls = classtModel()
cls.classfunc2()
- 使用
@_dynamicReplacement(for: 要替換的方法名)
關鍵字,可以實現oc中的方法交換