swift中 class與struct的方法調(diào)用

swift為了實(shí)現(xiàn)快這么一個(gè)終極目標(biāo)嘱兼。在許多地方做了大量的優(yōu)化成艘。簡(jiǎn)直可以說(shuō)是集現(xiàn)代編程語(yǔ)言之長(zhǎng)爪飘。而這一點(diǎn)在swift中的方法調(diào)用尤為突出梧喷。我們來(lái)探究一下swift中的方法調(diào)用砌左。

swift中函數(shù)調(diào)用方試

swift語(yǔ)言中總共有三種方法調(diào)用方式:
1.通過(guò)內(nèi)存地址直接調(diào)用
2.通過(guò)v-table這么一個(gè)結(jié)構(gòu)類似數(shù)組的函數(shù)表調(diào)用
3.就是我們非常熟悉的send_msg()消息派發(fā)。
他們的調(diào)用效率是1>2>3铺敌。動(dòng)態(tài)性則是3>2>1汇歹。然后swift在任何時(shí)候都會(huì)優(yōu)先的盡量使用內(nèi)存直接調(diào)用,不能夠使用內(nèi)存地址直接調(diào)用的時(shí)候使用函數(shù)表調(diào)用偿凭。那么什么時(shí)候不能夠使用內(nèi)存直接調(diào)用了产弹,很簡(jiǎn)單函數(shù)可能會(huì)被重寫時(shí)就不能夠使用內(nèi)存直接調(diào)用了,而第三種消息表派發(fā)就是需要使用到runtime的時(shí)候會(huì)使用

stuct的方法調(diào)用

image.png

stuct一把都是采用的函數(shù)地址調(diào)用弯囊。在匯編代碼中bl表示跳轉(zhuǎn)到地址痰哨。這就效率最高的函數(shù)調(diào)用方式不用查找,擼起地址直接干匾嘱。但是上面我們看的func2方法添加了一個(gè)mutating的關(guān)鍵字斤斧,要知道它的不同我們進(jìn)入到sil中,能更詳細(xì)的看的swift中做了什么

struct Person {
  func func1()
  mutating func func2()
  init()
}
.....
// Person.func1()
sil hidden [ossa] @$s14ViewController6PersonV5func1yyF : $@convention(method) (Person) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $Person):
  debug_value %0 : $Person, let, name "self", argno 1 // id: %1

.....
// Person.func2()
sil hidden [ossa] @$s14ViewController6PersonV5func2yyF : $@convention(method) (@inout Person) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $*Person):
  debug_value_addr %0 : $*Person, var, name "self", argno 1 // id: %1

我截取了部分關(guān)鍵的sil代碼霎烙。在這段代碼里面 func1()和func2()第一個(gè)不同點(diǎn)在@convention(method) (@inout Person) 中,func2()方法多了一個(gè)@inout撬讽,第二個(gè)不同點(diǎn)在Person, var, name "self", argno 1。里面的Person取得是地址悬垃。
inout 的作用很簡(jiǎn)單可以讓let 定義的變量可以修改

func func3(age:inout Int){
        age = 20
        print(age)
    }

我們都知道在swift是不可以直接修改參數(shù)值的如果你要強(qiáng)行修改就會(huì)報(bào)這個(gè)錯(cuò)


image.png

但是如果我添加了inout關(guān)鍵字就可以修改這個(gè)let定義的變量了游昼。調(diào)用inout修飾的參數(shù)的方法。參數(shù)需要傳地址盗忱。像這樣調(diào)用func3(age: &leoAge)酱床。
那么在sil中func2()方法這里swift隱式的添加@inout的目的也就很明顯了。意思就是可以修改傳遞進(jìn)來(lái)的Person的參數(shù)趟佃。也就是struct本身扇谣。這就是mutaiting修飾的方法可以修改struct變量的原因。
struct中只有內(nèi)存直接調(diào)用與函數(shù)表調(diào)用兩種調(diào)用方式闲昭。因?yàn)橐鞘褂孟⑴砂l(fā)必需繼承自NSObject對(duì)象罐寨。

函數(shù)表調(diào)用

函數(shù)表的方式調(diào)用函數(shù),在下面的截圖中我們可以看到func2()的blr后面跟的不是函數(shù)地址而是一個(gè)變量序矩。那這個(gè)變量swift是怎樣存儲(chǔ)的了鸯绿?我們知道它是怎樣存儲(chǔ)的就知道了它是怎樣調(diào)用的了。我們嘗試著來(lái)找到函數(shù)表中函數(shù)地址的存儲(chǔ)方式。

image.png

下面我們需要查看macho文件格式瓶蝴,我們先簡(jiǎn)單介紹一下macho文件的格式
Macho文件格式是這樣的


image.png

它主要分為三個(gè)部分:1.Header 2.Load commands 3Data區(qū)
header中表明該文件是 Mach-O 格式毒返,指定目標(biāo)架構(gòu),還有一些其他的文件屬性信 息舷手,文件頭信息影響后續(xù)的文件結(jié)構(gòu)
Load commands是一張包含很多內(nèi)容的表拧簸。內(nèi)容包括區(qū)域的位置、符號(hào)表男窟、動(dòng)態(tài)符號(hào)表 等盆赤。
Data 區(qū)主要就是負(fù)責(zé)代碼和數(shù)據(jù)記錄的。Mach-O 是以 Segment 這種結(jié)構(gòu)來(lái)組織數(shù)據(jù) 的歉眷,一個(gè) Segment 可以包含 0 個(gè)或多個(gè) Section牺六。根據(jù) Segment 是映射的哪一個(gè) Load Command,Segment 中 section 就可以被解讀為是是代碼汗捡,常量或者一些其他的數(shù)據(jù)類 型淑际。在裝載在內(nèi)存中時(shí),也是根據(jù) Segment 做內(nèi)存映射的凉唐。

我們?cè)诳匆幌聅wift的類的構(gòu)成庸追,有助于我們?cè)趍achoc中查找我們需要的信息。swift中類都有一個(gè)元數(shù)據(jù)結(jié)構(gòu)台囱,這個(gè)數(shù)據(jù)結(jié)構(gòu)是一個(gè)Metadata 的struct淡溯。通過(guò)swift的源碼我們可以推導(dǎo)出Metadata的內(nèi)部結(jié)構(gòu)是這樣的

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 
}

其中我們需要關(guān)注typeDescriptor,不管是Class、Struct簿训、Enumd都有Descriptor.用來(lái)描述它自身咱娶。類的描述對(duì)象如下

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
...
}

我們都知道struct是值類型。如果我們現(xiàn)在能找到typeDescriptor這個(gè)指針地址强品,通過(guò)指針偏移就能訪問(wèn)到struct中值類型的變量的值或者引用類型的指針膘侮。那么v-table這個(gè)函數(shù)表我們推測(cè)它與位于size的后面。因?yàn)樗绻辉谶@個(gè)地方我們考慮不到它能存放在什么地方了的榛。下面我們來(lái)驗(yàn)證一下琼了。如果能找到就說(shuō)明確實(shí)是這樣的。

1.我們用machoview打開(kāi)我們編譯后的可執(zhí)行文件夫晌。

image.png

我們直接定位到數(shù)據(jù)段中的__TEXT,__swift5_types中雕薪。__TEXT,__swift5_types就是存放TargetClassDescriptor的地方。pFile是虛擬內(nèi)存地址晓淀,我們來(lái)計(jì)算一下前面8位的值所袁。0x0000BBDC+0x9CFBFFFF,注意這個(gè)地方的0x9CFBFFFF是小段地址所以應(yīng)該是0xFFFFFB9C。我們得到的值是0x10000B778凶掰。然后這個(gè)值我們需要減去程序本身虛擬內(nèi)存得地址燥爷,這個(gè)值在Load Commands中的LC_SEGMENT_64(__TEXT)中蜈亩。


image.png

可以看到這個(gè)值是0x100000000。我們現(xiàn)在得到的值是0x0000B778前翎,我們到data段中,在Section64(__TEXT,__const)中我們能看到0x0000B778位于的內(nèi)存區(qū)間稚配。


image.png

那么B778應(yīng)該就是在0xB770偏移8位,那么0x80000050就應(yīng)該是我們typeDescriptor的地址港华。

swift 方法的結(jié)構(gòu)

image.png

struct TargetMethodDescriptor就是swift中函數(shù)的數(shù)據(jù)結(jié)構(gòu)药有。其中第一個(gè)flags描述了函數(shù)的類型。


image.png

然后我們根據(jù)TargetClassDescriptor的結(jié)構(gòu)在來(lái)偏移13個(gè)字節(jié)我們?cè)诘刂?xB7B0找到了我們的TargetRelativeDirectPointer的偏移值苹丸。0x00000010ffffc5cc,根據(jù)TargetMethodDescriptor的數(shù)據(jù)結(jié)構(gòu)苇经,前面4個(gè)字節(jié)存放的是方法類型所以我們的函數(shù)本身的impl這個(gè)地方的偏移值應(yīng)該是0xFFFFC5CC赘理。我們當(dāng)前程序的ASLR的地址為0x1025d0000

0xFFFFC5CC+0x0000B7b0 = 0x100007d7c
0x100007d7c+0x1025d0000 = 0x2025D7D7C
0x2025D7D7C - Vm地址(0x10000000) = 0x1025D7D7C
下面看代碼

image.png

我們我們斷點(diǎn)到leo.fun2()這里查看匯編代碼,blr x8 其x8寄存器存放的就是func2()的地址扇单。我們使用register read x8讀取x8的地址 0x00000001025d7d7c商模。與我們計(jì)算得到的地址是一樣的說(shuō)明我們上面的推論是正確的。

消息派發(fā)

class Person:NSObject{
    
    func func2(){
        
    }
    
    @objc dynamic func func3(){
        
    }
    
    @objc func func4(){
        
    }
    
    dynamic func func5(){
        
    }
}


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let leo = Person()
        leo.func2()
        leo.func3()
        leo.func4()
        leo.func5()
    }
}

我在Person中又添加了func3()蜘澜、func4()施流、func5()方法。同時(shí)對(duì)他們分別使用@objc 鄙信、dynamic修飾
然后我們看sil代碼


image.png

在Person的sil_vtable中func3()方法沒(méi)有添加到vtable這個(gè)函數(shù)表中瞪醋。因?yàn)閒unc3()使用了@objc + danamic 修飾。


image.png

在sil中 這個(gè)地方就很明顯的標(biāo)識(shí)出來(lái)了他們調(diào)用方法的不同装诡。func3()使用的是objc_method,而虛函數(shù)表使用的是class_method.
swift中的消息派發(fā)主要是為了兼容runtime的機(jī)制银受。所以使用消息派發(fā)的swift方法也就可以使用我們r(jià)untime的黑魔法了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸦采,一起剝皮案震驚了整個(gè)濱河市宾巍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渔伯,老刑警劉巖顶霞,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锣吼,居然都是意外死亡选浑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門吐限,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲜侥,“玉大人,你說(shuō)我怎么就攤上這事诸典∶韬” “怎么了崎苗?”我有些...
    開(kāi)封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舀寓。 經(jīng)常有香客問(wèn)我胆数,道長(zhǎng),這世上最難降的妖魔是什么互墓? 我笑而不...
    開(kāi)封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任必尼,我火速辦了婚禮,結(jié)果婚禮上篡撵,老公的妹妹穿的比我還像新娘判莉。我一直安慰自己,他們只是感情好育谬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布券盅。 她就那樣靜靜地躺著,像睡著了一般膛檀。 火紅的嫁衣襯著肌膚如雪锰镀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天咖刃,我揣著相機(jī)與錄音泳炉,去河邊找鬼。 笑死嚎杨,一個(gè)胖子當(dāng)著我的面吹牛花鹅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枫浙,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼翠胰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了自脯?” 一聲冷哼從身側(cè)響起之景,我...
    開(kāi)封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膏潮,沒(méi)想到半個(gè)月后锻狗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焕参,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年轻纪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叠纷。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刻帚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩嚣,到底是詐尸還是另有隱情崇众,我是刑警寧澤掂僵,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站顷歌,受9級(jí)特大地震影響锰蓬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眯漩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一芹扭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赦抖,春花似錦舱卡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浮禾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間份汗,已是汗流浹背盈电。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杯活,地道東北人匆帚。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像旁钧,于是被迫代替她去往敵國(guó)和親吸重。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容