說(shuō)說(shuō)NSObject的 performSelector 系列函數(shù)
記錄調(diào)試這個(gè)bug的過(guò)程
說(shuō)說(shuō)遇到的bug
?????說(shuō)之前先說(shuō)說(shuō)遇到的bug,公司項(xiàng)目,Target最低終于支持iOS8以上,web頁(yè)以前使用的是UIWebView,iOS8之后,蘋(píng)果推出了WKWebView,新寫(xiě)的功能需要使用到WKWebView,在之前使用UIWebView的時(shí)候,H5為了兼容安卓和iOS,會(huì)做平臺(tái)類(lèi)型判斷,這次團(tuán)隊(duì)決定使用iOS,安卓和H5通用的一套javaScript和原生的APP交互SDK,最終挑選了DSBridge,文檔上說(shuō)是一款三端易用的現(xiàn)代跨平臺(tái) Javascript bridge, 通過(guò)它,你可以在Javascript和原生之間同步或異步的調(diào)用彼此的函數(shù),
????? 聯(lián)調(diào)過(guò)后,一切完美,同步,異步方式都可以調(diào)用,但是最終打包給測(cè)試同學(xué)的時(shí)候,崩潰了,經(jīng)過(guò)一番檢查,發(fā)現(xiàn)在同步調(diào)用情況下崩潰,并且在DEBUG模式下沒(méi)有崩潰,在RElease模式下崩潰,javascript回調(diào)客戶(hù)端注冊(cè)的方法的時(shí)候直接 EXC_BAD_ACCESS 經(jīng)過(guò)一番檢查,在同步或者異步方式回調(diào)的時(shí)候使用了 performSelector這個(gè)方法,先看看DSBridge部分源代碼:
-(NSString *)call:(NSString*) method :(NSString*) argStr {
...只貼重要部分
// 異步調(diào)用
SuppressPerformSelectorLeakWarning(
[JavascriptInterfaceObject performSelector:selasyn withObject:arg withObject:completionHandler];
);
// 同步調(diào)用
id ret;
SuppressPerformSelectorLeakWarning(
ret=[JavascriptInterfaceObject performSelector:sel withObject:arg];
);
}
?????DSBridge這樣設(shè)計(jì),會(huì)因?yàn)楫惒秸{(diào)用的時(shí)候客戶(hù)端通過(guò)completionHandler直接回調(diào)web端,而同步的時(shí)候得到一個(gè)返回值,在執(zhí)行js,通過(guò)js回調(diào)給web端,其中JavascriptInterfaceObject是客戶(hù)端注冊(cè)的調(diào)用對(duì)象,可以看到使用了performSelector,同事確定release模式下崩潰,我看到EXC_BAD_ACCESS 首先想到的是某個(gè)對(duì)象被釋放掉了,但是又給這個(gè)對(duì)象發(fā)了消息,但是日志都能正確,一點(diǎn)一點(diǎn)定位位置,直到無(wú)意中將同步調(diào)用方法的接收參數(shù)ret 去掉,才發(fā)現(xiàn)沒(méi)有崩潰了,確定本次崩潰和performSelector有關(guān)。
?????仔細(xì)想想performSelector函數(shù)到底返回什么,說(shuō)不清楚,NSObject.h中沒(méi)有注釋?zhuān)臋n中寫(xiě)的返回值是void,函數(shù)定義的返回的是一個(gè)id類(lèi)型。于是寫(xiě)一個(gè)Demo測(cè)試,在debug模式下,給有個(gè)對(duì)象通過(guò)performSelector發(fā)送2個(gè)消息,這2個(gè)方法分別是方法A和方法B,方法A有返回值,方法B返回值為void,得到結(jié)果方法A的返回值是調(diào)用函數(shù)的返回值,調(diào)用方法B 直接崩潰。終于確定問(wèn)題。
?????為什么會(huì)崩潰呢,和調(diào)用方法是否有返回值有關(guān),以前也知道performSelector編譯器不會(huì)對(duì)對(duì)象,方法,進(jìn)行檢驗(yàn),會(huì)有內(nèi)存泄露的可能產(chǎn)生,performSelector會(huì)把編譯時(shí)做的事情放到了運(yùn)行時(shí)期,點(diǎn)擊這里去看一些performSelector的詳細(xì)介紹,因?yàn)椴恢兰磳⒄{(diào)用的selector是否有返回值,只有到了運(yùn)行期才去檢測(cè),調(diào)用了返回值為Void的一些函數(shù),相當(dāng)于直接是 id obj = void,當(dāng)然是OC語(yǔ)法不允許的,直接崩潰是必然的。
?????為什么我們自己的項(xiàng)目在Debug模式下正常呢,于是在項(xiàng)目中寫(xiě)了局代碼測(cè)試,發(fā)現(xiàn)會(huì)返回一個(gè)nil,猜測(cè)可能在哪個(gè)地方在進(jìn)行了方法交換,查了代碼,沒(méi)有查到,可能是在某個(gè)SDK中,如圖:

總結(jié)
本次調(diào)試bug慢比較慢的原因:
- 對(duì)performSelector只知道怎么去用,細(xì)節(jié)部分沒(méi)想過(guò),比如函數(shù)的返回值,將編譯時(shí)期可以報(bào)出的錯(cuò)誤放到了運(yùn)行時(shí)期,等等
- DSBridge源碼看過(guò),但是看的不仔細(xì),花了好一會(huì)才了解整體調(diào)用過(guò)程。
- 查bug的時(shí)候會(huì)去猜測(cè),可能是哪個(gè)原因,然后通過(guò)注釋代碼來(lái)找問(wèn)題(知識(shí)掌握的不牢固)
- 在使用第三方庫(kù)的時(shí)候一定要讀懂核心部分源碼,在遇到問(wèn)題的時(shí)候才能夠快速定位問(wèn)題。
歸根結(jié)底都是對(duì)知識(shí)掌握的不牢固,踩的坑還不夠多。
所有的bug都有他們必現(xiàn)的原因,只是我沒(méi)有找到原因而已。
我以為的不一定是我以為的
????? 原本以為已經(jīng)解決了這個(gè)問(wèn)題,但是自己寫(xiě)Demo的時(shí)候是在模擬器上測(cè)試,公司項(xiàng)目是在真機(jī)上測(cè)試,都在真機(jī)上運(yùn)行都沒(méi)有崩潰,寫(xiě)的demo也返回了第一個(gè)固定參數(shù),公司項(xiàng)目傳入?yún)?shù){"action":"customservice"} 就直接崩潰 在真機(jī)上和在模擬器上有什么不同嗎?模擬器和真機(jī)都是iOS 11.3系統(tǒng),自己公司的項(xiàng)目為什么會(huì)發(fā)生崩潰呢?