分析用例
我們拿一個(gè)純Swift類和一個(gè)繼承自NSObject的類的類來做分析,這兩個(gè)類里包含盡量多的Swift的類型比如Character、String垛玻、AnyObject摹察、Tuple。
代碼如下:
方法碧囊、屬性
動(dòng)態(tài)性比較重要的一點(diǎn)就是能夠拿到某個(gè)類所有的方法树灶、屬性,我們使用如下代碼來打印方法和屬性列表糯而。
調(diào)用showClsRuntime的代碼如下:
看看我們得到什么結(jié)果天通?
對(duì)于純Swift的TestASwiftClass來說任何方法、屬性都未獲取到熄驼。
對(duì)于TestSwiftVC來說除testReturnTuple像寒、testReturnVoidWithaCharacter兩個(gè)方法外,其他的都獲取成功了瓜贾。
這是為什么诺祸?
純Swift類的函數(shù)調(diào)用已經(jīng)不再是Objective-c的運(yùn)行時(shí)發(fā)消息,而是類似C++的vtable祭芦,在編譯時(shí)就確定了調(diào)用哪個(gè)函數(shù)筷笨,所以沒法通過runtime獲取方法、屬性龟劲。
TestSwiftVC繼承自UIViewController胃夏,基類NSObject,而Swift為了兼容Objective-C咸灿,凡是繼承自NSObject的類都會(huì)保留其動(dòng)態(tài)性构订,所以我們能通過runtime拿到他的方法。
但為什么testReturnTuple?testReturnVoidWithaCharacter卻又獲取不到呢避矢?
從Objective-c的runtime 特性可以知道悼瘾,所有運(yùn)行時(shí)方法都依賴TypeEncoding囊榜,也就是method_getTypeEncoding返回的結(jié)果,他指定了方法的參數(shù)類型以及在函數(shù)調(diào)用時(shí)參數(shù)入棧所要的內(nèi)存空間亥宿,沒有這個(gè)標(biāo)識(shí)就無法動(dòng)態(tài)的壓入?yún)?shù)(比如testReturnVoidWithaId: Optional("v24@0:8@16") Optional("v")卸勺,表示此方法參數(shù)共需24個(gè)字節(jié),返回值為void烫扼,第一個(gè)參數(shù)為id曙求,第二個(gè)為selector,第三個(gè)為id)映企,而Character和Tuple是Swift特有的悟狱,無法映射到OC的類型,更無法用OC的typeEncoding表示堰氓,也就沒法通過runtime獲取了挤渐。
Method Swizzling
動(dòng)態(tài)性最常用的就是方法替換(Method Swizzling),將類的某個(gè)方法替換成自定義的方法双絮,從而達(dá)到hook的作用浴麻。
對(duì)于純Swift類(如TestASwiftClass)來說,無法通過objc runtime替換方法囤攀,因?yàn)橛缮厦娴臏y(cè)試可知拿不到這些方法软免、屬性
對(duì)于繼承自NSObject類(如TestSwiftVC)來說,無法通過runtime獲取到的方法肯定沒法替換了焚挠。那能通過runtime獲取到的方法就都能被替換嗎膏萧?我們測(cè)一把。
Method Swizzling的代碼如下
我們替換兩個(gè)可以被runtime獲取到的方法:viewDidAppear和testReturnVoidWithaId
打印的日志為
F:testReturnVoidWithaId L:50
F:sz_viewDidAppear L:46
說明viewDidAppear已經(jīng)被替換蝌衔,但是testReturnVoidWithaId卻沒有被替換向抢,這是為何?
我們?cè)诜椒ɡ锎騻€(gè)斷點(diǎn)看看,如圖:
可以看到區(qū)別胚委,調(diào)用sz_viewDidAppear棧的前一幀為@objc TestSwiftVC.sz_viewDidAppear(Bool) -> ()有個(gè)@objc標(biāo)識(shí),而調(diào)用testReturnVoidWithaId則沒有此標(biāo)識(shí)叉信。
@objc用來做什么的亩冬?與動(dòng)態(tài)性有關(guān)嗎?
@objc
找到官方文檔讀讀硼身。
可以知道@objc是用來將Swift的API導(dǎo)出給Objective-C和Objective-C runtime使用的硅急,如果你的類繼承自O(shè)bjective-c的類(如NSObject)將會(huì)自動(dòng)被編譯器插入@objc標(biāo)識(shí)。
我們?cè)诎裈estASwiftClass(純Swift類)的方法佳遂、屬性前都加個(gè)@objc 試試营袜,如圖:
查看日志可以發(fā)現(xiàn)加了@objc的方法、屬性均可以被runtime獲取到了丑罪。
dynamic
文檔里還有一句說明:
加了@objc標(biāo)識(shí)的方法荚板、屬性無法保證都會(huì)被運(yùn)行時(shí)調(diào)用凤壁,因?yàn)镾wift會(huì)做靜態(tài)優(yōu)化。要想完全被動(dòng)態(tài)調(diào)用跪另,必須使用dynamic修飾拧抖。使用dynamic修飾將會(huì)隱式的加上@objc標(biāo)識(shí)。
這也就解釋了為什么testReturnVoidWithaId無法被替換免绿,因?yàn)閷懺赟wift里的代碼直接被編譯優(yōu)化成靜態(tài)調(diào)用了唧席。
而viewDidAppear是繼承Objective-C類獲得的方法,本身就被修飾為dynamic嘲驾,所以能被動(dòng)態(tài)替換淌哟。
我們把TestSwiftVC方法前加上dynamic再測(cè)一把,如圖:
從堆棧也可以看出辽故,方法的調(diào)用前增加了@objc標(biāo)識(shí)丙猬,testReturnVoidWithaId方法被替換成功了。
同樣的做法孔轴,我們把TestASwiftClass的方法和屬性也都加上dynamic修飾杀糯,做Method Swizzling,同樣獲得成功彤枢,如圖
Objective-C獲取Swift runtime信息
在Objective-c代碼里使用objc_getClass("TestSwiftVC");會(huì)發(fā)現(xiàn)返回值為空狰晚,這是為什么?Swift代碼中的TestSwiftVC類缴啡,在OC中還是這個(gè)名字嗎壁晒?
我們初始化一個(gè)對(duì)象,并斷點(diǎn)和打印看看业栅,如下圖:
可以看到Swift中的TestSwiftVC類在OC中的類名已經(jīng)變成TestSwift.TestSwiftVC秒咐,即規(guī)則為SWIFT_MODULE_NAME.類名稱,在普通源碼項(xiàng)目里SWIFT_MODULE_NAME即為ProductName碘裕,在打好的Cocoa Touch Framework里為則為導(dǎo)出的包名携取。
所以要想從Objective-c中獲取Swift類的runtime信息得這樣寫:
Objective-C替換Swift函數(shù)
給TestSwiftVC和TestASwiftClass的testReturnVoidWithaId函數(shù)加上dynamic修飾,然后我們?cè)贠bjective-C代碼里替換為testReturnVoidWithaIdImp函數(shù):
運(yùn)行之后我們得到結(jié)果
F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=
F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=TestSwift.TestASwiftClass
說明兩者的方法在加上dynamic修飾后帮孔,均能在Objective-c里被替換雷滋。(TestSwiftVC的testReturnVoidWithaId不加dynamic也會(huì)打印日志,為什么文兢?留給讀者思考)
總結(jié)
純Swift類沒有動(dòng)態(tài)性晤斩,但在方法、屬性前添加dynamic修飾可以獲得動(dòng)態(tài)性姆坚。
繼承自NSObject的Swift類澳泵,其繼承自父類的方法具有動(dòng)態(tài)性,其他自定義方法兼呵、屬性需要加dynamic修飾才可以獲得動(dòng)態(tài)性兔辅。
若方法的參數(shù)腊敲、屬性類型為Swift特有、無法映射到Objective-C的類型(如Character幢妄、Tuple)兔仰,則此方法、屬性無法添加dynamic修飾(會(huì)編譯錯(cuò)誤)
Swift類在Objective-C中會(huì)有模塊前綴?
轉(zhuǎn)載:http://www.chinaz.com/news/2016/0408/520403.shtml