深入瞭解Swift

Swift是蘋果新的編程語言,很多人都認爲它可以“替換”Objective-C,但事實卻並非如此。我花了一些時間對Swift運行時和二進制文件進行了逆向工程,不過(不過錶轉折,可去掉)發現了很多有趣的事。總結起來就是:Swift是沒有消息機制的Objective-C。
 
面向對象
不管你信不信,Swift對象實際上是Objective-C對象。在Mach-O二進制文件中可以發現__objc_classlist部分包含了每個類的二進制數據,結構類似:
  1. struct objc_class { 
  2.     uint64_t isa; 
  3.     uint64_t superclass; 
  4.     uint64_t cache; 
  5.     uint64_t vtable; 
  6.     uint64_t data; 
  7. }; 
 (注:所有的結構都是在64位機器上編譯)
 
注意數據入口,它指向一個列出了類的方法、實例變量以及協議等等的結構。通常情況下,數據是8個字節對齊的。然而,在Swift的類中,最後一個數據將只佔有1個字節。
 
Swift類的實際結構有點奇怪。Swift類沒有類似於Objective-C的方法,這點會在後面說明。Swift類中變量被存儲爲一個實例變量。在Swift中利用getter和setter方法修改這個實例變量的值(譯者注:此處的getter和setter並不實際存在,只是抽象爲實例變量的訪問和修改入口,是一種規範,在下文中不對getter和setter進行特別翻譯)。奇怪的是,Swift類的實例變量其實是沒有類型編碼。通常應該指向類型編碼的指針爲NULL,這想必是因爲Objective-C的運行時不會親自去處理Swift變量。
 
繼承
正如你所期望的那樣,Swift同樣擁有類的繼承與派生。比如,在Swift中,Shape(形狀)的子類Square(方形)在Objective-C類中同樣是Shape的子類。然而,如果Swift中的類沒有父類會怎樣?
 
例如:
  1. class Shape { } 
在這種情況下,Shape類是一個SwiftObject的子類。而類swiftobject是一個類似於Objective-C中NSObject這樣的一個基類。它沒有父類,這意味着isa變量指向其本身。其目的是利用Swift運行時方法處理像分配和釋放內存這樣的事,而不是標準的Objective-C運行時。比如說,改爲- (void)retain不是調用objc_retain,而是調用swift_retain
 
類方法
就像我前面提到的,Swift對象的類沒有方法。相反,他們使用像C++一樣的函數、重載以及其他替代。這可能是爲什麼Swift要比Objective-C快得多;這裏不怎麼需要用objc_msgsend來尋找和調用方法的實現。
 
在Objective-C中,方法實現類似這樣:
type method(id self, SEL _cmd, id arg1, id arg2, ...)
 
Swift方法是非常相似的,但稍有不同的是參數佈局,self只能作爲最後一個參數傳遞,這裏沒有選擇器。
type method(id arg1, id arg2, ..., id self)
 
虛函數表
就像在C++一樣,Swift的類有一個虛函數表,在虛表裏列出了類中的方法。它位於二進制的數據後面,看起來像這樣:
  1. struct swift_vtable_header { 
  2.     uint32_t vtable_size; 
  3.     uint32_t unknown_000; 
  4.     uint32_t unknown_001; 
  5.     uint32_t unknown_002; 
  6.     void* nominalTypeDescriptor; 
  7.     // vtable pointers 
這裏需要說明的是,Swift類的虛函數表只能當它在編譯時可見的情況下使用。否則將會報錯。
 
Name Mangling(命名重整)
Swift在各自的標誌中保存元數據的函數(以及更多),這就是所謂的name mangling。該元數據包括函數名,屬性、模塊名稱,參數類型,返回類型等等。
  1. class Shape{ 
  2.     func numberOfSides() -> Int { 
  3.         return 5 
  4.     } 
以此爲例:Simpledescriptio方法改編(重載)的名字是_tfc9swifttest5shape17simpledescriptionfs0_ft_si。這裏分解下:
t–所有Swift符號的前綴。所有事情皆始於次。
F–函數。
C一類的函數。(方法)
9swifttest–帶有一個前綴長度的模塊名稱。
5shape–函數所屬類的名稱,帶有一個前綴長度。
17simpledescription–函數名。
f–函數屬性。在本例中,它是“f”,這是一個常見的函數。
s0_ft–我不確定這意味着什麼,但它似乎是標記參數的開始,並返回類型。
“_”該下劃線把參數類型和返回類型分別開來。由於函數沒有參數,所以它直接跟在S0_FT後邊。
S--這是返回類型的開始。“S”代表Swift;返回類型是一個Swift內建類型。下一個字符決定類型。
i–這是Swift的內建類型。一個小寫的“i”,可代替int (小寫的“I”代表Int)。
 
函數屬性
 
Swift的內置命令
 
  
命名重整並不僅僅只用於函數,但我只是給出一個簡短的概述。
 
函數連接
這句子有足夠的語義,讓我們來到一個有趣的部分!如果我們有這樣的一個類:
  1. class Shape { 
  2.     var numberOfSides: Int; 
  3.    init(){ 
  4.            numberOfSides = 5; 
  5.        } 
  6.    } 
 
我們說我們想把numberofsides 改爲4。有多種方式來做這個。我們可以使用MobileSubstrate連接到getter方法,並改變返回值,像這樣:
  1. int (*numberOfSides)(id self); 
  2.  
  3. MSHook(int, numberOfSides, id self){ 
  4.     return 4; 
  5.  
  6. %ctor{ 
  7.     numberOfSides = (int (*)(id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapeg13numberOfSidesSi"); 
  8.     MSHookFunction(numberOfSides, MSHake(numberOfSides)); 
如果我們創建一個Shape實例,並打印出變量numberofsides的值,我們看到結果爲4!這並不是很壞,是吧?現在,我知道你在想什麼;“難道不是應該返回一個對象,而不是字符“4”嗎?”
 
的確,在Swift中,大量的內建類型都是文本。例如Int,與C中的Int一樣。注意,字符串類型有點奇怪,這是一個little-endian UTF-16字符串,所以沒有可以使用的C字面量。
 
讓我們做同樣的事情,但是這一次,我們會連接setter代替getter。
  1. void (*setNumberOfSides)(int newNumber, id self); 
  2.  
  3. MSHook(void, setNumberOfSides, int newNumber, id self){ 
  4.     _setNumberOfSides(4, self); 
  5.  
  6.  
  7.  
  8. %ctor { 
  9.     setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapes13numberOfSidesSi"); 
  10.     MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides)); 
再試試跑一次程序吧…結果仍然是5。你一定會問發生什麼事了?這是因爲,在Swift的某些地方函數是內聯的。該類的構造函數是一個這樣的函數。它直接設置numberofsides ivar。所以,只有數值在頂部的代碼中被再次設置時纔會調用setter方法。執行調用後,我們會的到4這個值。
 
最後,讓我們通過直接設置變量實例改變numberofsides的值。
  1. void (*setNumberOfSides)(int newNumber, id self); 
  2.  
  3. MSHook(void, setNumberOfSides, int newNumber, id self){ 
  4.     MSHookIvar<int>(self, "numberOfSides") = 4; 
  5.  
  6.  
  7.  
  8. %ctor { 
  9.     setNumberOfSides = (void (*)(int newNumber, id self)) dlsym(RTLD_DEFAULT, "_TFC9swifttest5Shapes13numberOfSidesSi"); 
  10.     MSHookFunction(setNumberOfSides, MSHake(setNumberOfSides)); 
這樣同樣可以達到效果。雖然並不推薦這樣做,但它仍是可行的解決方案。
 
這就是我要寫的所有東西。雖然有很多其他的東西,但我不知道是否有足夠精力去寫。在這篇文章中許多東西是會變化的。它們只是我使用Swift從運行時和二進制文件逆向工程得到的。但我覺得這還不錯。這意味着,MobileSubstrate不會隨着Objective-C消亡,仍有調整的空間。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章