可能大家一直看到有許多朋友在Runtime相關文章中介紹IMP指針的概念,那麼IMP究竟有什麼實際作用呢?讓我們先從一個函數看起來。
Method Swizzling
如果對Runtime有一定了解的話,一定聽說過或者用過這個函數:
void method_exchangeImplementations(Method m1, Method m2)
它通常叫做method swizzling,算是ObjC的"黑魔法"了,作用就是在程序運行期間動態的給兩個方法互換實現,比如有這樣一種使用場景:
我們的程序中有許多個ViewController,我想在對項目改動最小的情況下,在當每個Controller執行完ViewDidLoad以後就在控制台把自己的名字打印出來,方便我去做調試或者了解項目結構。
有許多朋友會這樣說,讓所有控制器都繼承一個BaseController不就可以了嗎?我在這裡要解釋一下這樣做的缺點:假如你的項目裡有許多Controller的話,你就需要把項目裡凡是沒有繼承自BaseController的每個Controller都做一次修改了,而且隨意更改層級結構會發生意想不到的錯誤。
其實我們的目的就是重寫ViewDidLoad的方法,並在他的方法最後加上幾句Log,所以我們需要給UIViewController建立一個category,因為我們知道,如果在Catagory中重寫一個方法,就會覆蓋它的原有方法實現,但是,這樣做以後就沒有辦法調用系統原有的方法,因為在一個方法裡調用自己的方法會是一個死循環。所以我們的解決辦法就是,另外寫一個方法來和viewDidLoad“交換”,這樣外部調用viewDidLoad就會調到新建的這個方法中,同樣,我們調用新建的方法就會調用到系統的viewDidLoad中了。
IMP指針
其實,還有一種更加簡單的方法可以讓我們辦到相同的目的,運用IMP指針,IMP就是Implementation的縮寫,顧名思義,它是指向一個方法實現的指針,每一個方法都有一個對應的IMP,所以,我們可以直接調用方法的IMP指針,來避免方法調用死循環的問題。
調用一個IMP的方式和調用普通C函數相同,比如:
id returnObjc = someIMP(objc,SEL,params...);
不過如果你的項目沒有做其他配置的話這樣調用編譯器是不會通過的,我們來看一下先它的定義:
if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); else typedef id (*IMP)(id, SEL, ...); endif
在默認情況下你的工程是打開這個配置的
這種情況下IMP被定義為無參數無返回值的函數。所以你需要到工程裡搜索到這個選項並把它關閉。這樣的麻煩就是,每次使用,你都需要修改工程配置,所以這裡我再介紹另外一種辦法:重新定義一個和有參數的IMP指針相同的指針類型,在獲取IMP時把它強轉為此類型。這樣運用IMP指針後,就不需要額外的給ViewController寫新的方法:
還有一個地方我們需要注意,如果這樣直接調用IMP的話就會發生經典的EXC_BAD_ACCESS錯誤,我們定義的IMP指針是一個有返回值的類型,而其實我們獲取的viewDidLoad這個方法是沒有返回值的,所以我們需要新定義一個和IMP相同類型的函數指針比如VIMP,把他的返回值定位Void,這樣如果你修改的方法有返回值就用IMP,沒有返回值就用VIMP。
值得注意的是,如果你重寫的方法有返回值,不要忘記在最後做return。
總結
實際上直接調用一個方法的IMP指針的效率是高於調用方法本身的,所以,如果你有一個合適的時機獲取到方法的IMP的話,你可以試著調用它。
這是只是IMP使用的場景之一,它還有許多作用,希望大家多多發現。