淺談在Swift中關(guān)于函數(shù)指針的實(shí)現(xiàn)
Swift沒(méi)有什么?
蘋果工程師給我建的唯一一堵墻是:在Swift中沒(méi)有任何辦法獲得一個(gè)函數(shù)的指針:
注意,C函數(shù)指針不會(huì)導(dǎo)入到Swift中(來(lái)自“Using Swift with Cocoa and Objective-C“)
但是我們?cè)趺粗肋@種情況下鉤子的地址和跳到哪呢?讓我們深入了解一下,并且看看Swift的func在字節(jié)碼層面上的是什么。
當(dāng)你給一個(gè)函數(shù)傳遞一個(gè)泛型參數(shù)時(shí),Swift并沒(méi)有直接傳遞它的地址,而是一個(gè)指向trampoline函數(shù)(見(jiàn)下文)并帶有一些函數(shù)元數(shù)據(jù)信息的指針。并且trampoline自己是包裝原始函數(shù)的結(jié)構(gòu)的一部分。
這是什么意思?
讓我們用它來(lái)舉個(gè)例子:
func call_function(f : () -> Int) {
let b = f()
}
func someFunction() -> Int {
return 0
}
在Swift里我們只寫(xiě) call_function(someFunction).
但是 Swift 編譯器處理代碼后,性能比調(diào)用call_function(&someFunction)好很多
struct swift_func_wrapper *wrapper = ... /* configure wrapper for someFunction() */
struct swift_func_type_metadata *type_metadata = ... /* information about function's arguments and return type */
call_function(wrapper->trampoline, type_metadata);
一個(gè)包裝器的結(jié)構(gòu)如下:
struct swift_func_wrapper {
uint64_t **trampoline_ptr_ptr; // = &trampoline_ptr
uint64_t *trampoline_ptr;
struct swift_func_object *object;
}
什么是 swift_func_object類型? 為了創(chuàng)建對(duì)象,Swift 實(shí)時(shí)使用了一個(gè)全局的叫metadata[N]的的常量(每一個(gè) function調(diào)用都是唯一的,似的你的func 作為一個(gè)泛型的參數(shù),所以對(duì)于如下的代碼:
func callf(f: () -> ()) {
f();
}
callf(someFunction);
callf(someFunction);
常量metadata和metadata2會(huì)被創(chuàng)建).
一個(gè)metadata[N]的結(jié)構(gòu)有點(diǎn)兒像這樣this:
struct metadata {
uint64_t *destructor_func;
uint64_t *unknown0;
const char type:1; // I'm not sure about this and padding,
char padding[7]; // maybe it's just a uint64_t too...
uint64_t *self;
}
最初metadataN只有2個(gè)字段集合:destructor_func 和 type。前者是一個(gè)函數(shù)指針,將用作為使用swift_allocObject() 創(chuàng)建的對(duì)象分配內(nèi)存。后者是對(duì)象類型識(shí)別器(函數(shù)或方法的0x40 或者 '@'),并且是(某種形式)被swift_allocObject() 用來(lái)創(chuàng)建一個(gè)正確的對(duì)象給我們的func:
swift_allocObject(&metadata2->type, 0x20, 0x7);
一旦func 對(duì)象被創(chuàng)建,它擁有下面的結(jié)構(gòu):
struct swift_func_object {
uint64_t *original_type_ptr;
uint64_t *unknown0;
uint64_t function_address;
uint64_t *self;
}
第一個(gè)字段是一個(gè)指針,用來(lái)對(duì)應(yīng)metadata[N]->type 的值,第二個(gè)字段似乎是 0x4 | 1 << 24(0x100000004) 并且暗示一些可能 (我不知道是什么)。 function_address 是我們實(shí)際掛鉤感興趣的地方,并且self 是 (立即) 自己的指針 (如果我們的對(duì)象表示一個(gè)普通的函數(shù),這個(gè)字段是 NULL)。
好,那么這段我從框架開(kāi)始如何?事實(shí)上,我不明白為什么Swift運(yùn)行時(shí)需要它們,但不論如何,這就是它們?cè)鷳B(tài)的樣子:
void* someFunction_Trampoline(void *unknown, void *arg, struct swift_func_object *desc)
{
void* target_function = (void *)desc->function_address;
uint64_t *self = desc->self;
swift_retain_noresult(desc->self); // yeah, retaining self is cool!
swift_release(desc);
_swift_Trampoline(unknown, arg, target_function, self);
return unknown;
}
void *_swift_Trampoline(void *unknown, void *arg, void *target_function, void *self)
{
target_function(arg, self);
return unknown;
}
讓我們創(chuàng)建它
想象一下,在你的Swift代碼中有這些函數(shù):
func takesFunc<T>(f : T) {
...
}
func someFunction() {
...
}
而且你想像這樣生成它們:
takesFunc(someFunction)
這一行代碼會(huì)轉(zhuǎn)換成相當(dāng)大的C程序:
struct swift_func_wrapper *wrapper = malloc(sizeof(*wrapper));
wrapper->trampoline_ptr = &someFunction_Trampoline;
wrapper->trampoline_ptr_ptr = &(wrapper.trampoline);
wrapper->object = ({
// let's say the metadata for this function is `metadata2`
struct swift_func_object *object = swift_allocObject(&metadata2->type, 0x20, 0x7);
object->function_address = &someFunction;
object->self = NULL;
object;
});
// global constant for the type of someFunction's arguments
const void *arg_type = &kSomeFunctionArgumentsTypeDescription;
// global constant for the return type of someFunction
const void *return_type = &kSomeFunctionReturnTypeDescription;
struct swift_func_type_metadata *type_metadata = swift_getFunctionTypeMetadata(arg_type, return_type);
takesFunc(wrapper->trampoline_ptr, type_metadata);
結(jié)構(gòu)體“swift_func_type_metadata”很不透明,因此我也沒(méi)太多可以說(shuō)的。
回到函數(shù)指針
既然我們已經(jīng)知道函數(shù)怎樣作為一個(gè)泛型類型參數(shù)表示,讓我們借助這個(gè)打到你的目的:獲取一個(gè)真正指向函數(shù)的指針!
我們要做的只是需要注意,我們已經(jīng)擁有一個(gè)作為第一個(gè)參數(shù)傳遞的trampoline_ptr指針域地址,所以object域的偏移量只是0x8。其他的所有都很容易組合:
uint64_t _rd_get_func_impl(void *trampoline_ptr)
{
struct swift_func_object *obj = (struct swift_func_object *)*(uint64_t *)(trampoline_ptr + 0x8);
return obj->function_address;
}
看起來(lái)是時(shí)候?qū)憣?xiě)
rd_route(
_rd_get_func_impl(firstFunction),
_rd_get_func_impl(secondFunction),
nil
)
但我們?cè)鯓訌腟wift中調(diào)用這些C函數(shù)呢?
為此,我們將使用Swift非公開(kāi)的特性:允許我們提供給C函數(shù)一個(gè)Swift接口的@asmname屬性。用法如下:
@asmname("_rd_get_func_impl")
func rd_get_func_impl<Q>(Q) -> UInt64;
@asmname("rd_route")
func rd_route(UInt64, UInt64, CMutablePointer<UInt64>) -> CInt;
這就是我們?cè)赟wift中使用rd_route()需要的一切。
但是它不能處理任何函數(shù)!
也就是說(shuō),你不能用rd_route()鉤住任何帶有泛型參數(shù)的函數(shù)(這可能是Swift的bug,也可能不是,我還沒(méi)弄清楚)。但是你可以使用extensions輕松的覆蓋它們,直接指定參數(shù)的類型:
class DemoClass {
class func template <T : CVarArg>(arg : T, _ num: Int) -> String {
return "\(arg) and \(num)";
}
}
DemoClass.template("Test", 5) // "Test and 5"
extension DemoClass {
class func template(arg : String, _ num: Int) -> String {
return "{String}";
}
class func template(arg : Int, _ num: Int) -> String {
return "{Int}";
}
}
-- Your extension's methods for String and Int will be preferred over the original ones */
DemoClass.template("Test", 5) -- "{String}"
DemoClass.template(42, 5) -- "{Int}"
-- But for other types `template(T, Int)` will be used
DemoClass.template(["Array", "Item"], 5) --- "[Array, Item] and 5"
SWRoute
為了在Swift里輕松地勾住函數(shù),我創(chuàng)建了一個(gè)名為SWRoute的封裝體—它只是一個(gè)小類和一個(gè)我們之前寫(xiě)過(guò)的C函數(shù):
class SwiftRoute {
class func replace<MethodT>(function targetMethod : MethodT, with replacement : MethodT) -> Int
{
return Int(rd_route(rd_get_func_impl(targetMethod), rd_get_func_impl(replacement), nil));
}
}
注意,我們無(wú)償進(jìn)行類型檢查因?yàn)镾wift需要目標(biāo)方法和替換具有相同的MethoT類型。
而且我們也無(wú)法使用一個(gè)復(fù)制的原始實(shí)現(xiàn),因此我只能把nil作為另一個(gè)參數(shù)傳給函數(shù)rd_route()。如果你對(duì)如何把這個(gè)指針集成到Swift代碼有自己的看法,麻煩告訴我!
你可以在資源庫(kù)中找到大量SWRoute的實(shí)例。
這就是所有的了。
相關(guān)文章
Combine中錯(cuò)誤處理和Scheduler使用詳解
這篇文章主要為大家介紹了Combine中錯(cuò)誤處理和Scheduler使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12iOS Swift讀取本地json文件報(bào)錯(cuò)的解決方法
只要是app開(kāi)發(fā)者都知道,從服務(wù)器端獲得的數(shù)據(jù)要不就是json格式的數(shù)據(jù),要么就是xml格式的數(shù)據(jù),而這篇文章主要給大家介紹了關(guān)于iOS Swift讀取本地json文件報(bào)錯(cuò)的解決方法,需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Swift仿選擇電影票的效果并實(shí)現(xiàn)無(wú)限/自動(dòng)輪播的方法
這篇文章主要給大家介紹了關(guān)于Swift仿選擇電影票的效果并實(shí)現(xiàn)無(wú)限/自動(dòng)輪播的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08詳談swift內(nèi)存管理中的引用計(jì)數(shù)
下面小編就為大家?guī)?lái)一篇詳談swift內(nèi)存管理中的引用計(jì)數(shù)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09Swift UILable 設(shè)置內(nèi)邊距實(shí)例代碼
本文主要介紹Swift UILable 設(shè)置內(nèi)邊距,這里提供示例代碼供大家參考,有需要的小伙伴可以看下2016-07-07SpringBoot3.0集成Redis緩存的實(shí)現(xiàn)示例
緩存就是一個(gè)存儲(chǔ)器,常用 Redis作為緩存數(shù)據(jù)庫(kù),本文主要介紹了SpringBoot3.0集成Redis緩存的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03