2011-02-19 60 views
9

我正在尋找一種方法使NSInvocation調用特定的IMP。默認情況下,它會調用它可以找到的「最低」IMP(即,最近被覆蓋的版本),但是我正在尋找一種方法來使其從繼承鏈中較高的位置調用IMP。我想要調用的IMP是動態確定的,否則我可以使用super關鍵字或類似的東西。使NSInvocation調用特定的IMP

我的想法是使用-forwardInvocation:機制來捕獲消息(簡單和已經工作的),然後改變IMP如此這般以既不是super執行也不是最遠的後裔的實現方法。 (硬)

我發現遠程關閉的唯一東西是AspectObjectiveC,但這需要libffi,這使得它與iOS不兼容。理想情況下,我希望這是跨平臺的。

任何想法?

免責聲明:我只是嘗試


嘗試了蹦牀功能

所以我想我已經得到的東西大多是成立的@ bbum的想法;我有以下蹦牀是被通過class_addMethod()正確添加,並且它得到進入:

id dd_trampolineFunction(id self, SEL _cmd, ...) { 
    IMP imp = [self retrieveTheProperIMP]; 
    self = [self retrieveTheProperSelfObject]; 
    asm(
     "jmp %0\n" 
     : 
     : "r" (imp) 
     ); 
    return nil; //to shut up the compiler 
} 

我已經驗證了,無論是適當的自我,並正確IMP是前JMP正確的事情,並_cmd參數也正常進入。 (換句話說,我正確地添加了這個方法)。

但是,事情正在發生。我有時會發現自己跳到一種方法(通常不是正確的方法),其中無self_cmd。其他時候,我只會在EXC_BAD_ACCESS的途中崩潰。想法? (自從我在彙編中完成任何事情之後已經很長時間了......)我正在x86_64上測試它。

回答

3

既然你已經有了IMP,您只需一個辦法做到的方法調用來表示IMP一個非常原始前進。考慮到你願意使用類似解決方案的NSInvocation,那麼你也可以構建一個類似的代理類。

如果我面對這個問題,我會創建一個簡單的代理類,其中包含要調用的IMP和目標對象(您需要設置self參數)。然後,我會在彙編中編寫一個採用第一個參數的蹦牀函數,假設它是代理類的一個實例,抓取self,將它填充到包含參數0的寄存器中,將IMP和* JMPs作爲尾部呼叫。

在手蹦牀,你會再補充一點,蹦牀作爲一個IMP上要轉發到特定的IMP類代理任何選擇....

爲了實現任何一種這樣的通用機制,關鍵是避免任何與重寫棧幀有關的事情。避免C ABI。避免移動有關的論點。

4

NSInvocation只是消息發送的對象表示形式。因此,它不能像正常的消息發送那樣調用特定的IMP。爲了讓調用調用特定的IMP,您需要編寫一個通過IMP調用例程的自定義NSInvocation類,或者您必須編寫一個實現該行爲的蹦牀,然後創建一個代表給蹦牀的消息(即你基本上不會使用NSInvocation)。

3

未經檢驗的想法:

你能使用object_setClass()逼你想要的IMP的選擇?這是...

- (void)forwardInvocation:(NSInvocation *)invocation { 
    id target = [invocation target]; 
    Class targetClass = classWithTheImpIWant(); 
    Class originalClass = objc_setClass(target, targetClass); 
    [invocation invoke]; 
    objc_setClass(target, originalClass); 
} 
+0

+1一個非常有趣的想法。不幸的是,如果目標'IMP'調用任何其他方法,他們會轉到與目標'IMP'相同級別的版本,而不是最遠的後代。 – 2011-02-19 06:47:04

+2

更不用說,如果你以這樣的方式離開課堂,就會被同時併發的任何併發的破壞...(但是,我喜歡它!:) – bbum 2011-02-19 07:35:14

2

我認爲你最好的選擇是使用libffi。你有沒有在https://github.com/landonf/libffi-ios看到iOS的端口?我沒有嘗試過這個端口,但是我已經用Mac上的任意參數成功地調用了IMP。

看一看JSCocoa https://github.com/parmanoir/jscocoa它包括代碼,以幫助您從Method準備ffi_cif結構,它還包含一個版本libffi應該編譯iOS上的。 (有沒有任何測試)

3

長增加了事實後,以供參考:

你可以用私有API做到這一點。將這個類別在方便的地方:

@interface NSInvocation (naughty) 
-(void)invokeUsingIMP:(IMP)imp; 
@end 

,瞧,這不正是你所期望。我從Mike Ash的舊博客文章中挖出了這顆寶石。

像這樣的私人API技巧對研究或內部代碼非常有用。只要記住從你的appstore綁定版本中刪除它。

0

你或許應該看看我們是如何在https://github.com/tuenti/TMInstanceMethodSwizzler

基本上調配有一定方法的實現對對象的實例,你調酒一類的所有對象的方法,所以它叫時,它看起來在字典中提供什麼是必須爲目標對象調用的實現,如果沒有找到,則回退到原始實現。

您也可以使用private invokeWithImp:方法,但如果您打算將應用程序提交給商店,則不鼓勵這樣做。

0

您可以在新選擇器下使用class_addMethod將IMP添加到類中,並調用該選擇器。

雖然臨時方法不能刪除。