在日常開發(fā)中我經(jīng)常會調(diào)用responseToSelector這個方法,尤其是是在我們寫的協(xié)議的類中我們經(jīng)常會有這樣的判斷爬坑。最近重新看《編寫高質(zhì)量iOS于OS X代碼的52個有效方法》這本書的時候作者提供了不一樣的思路撑帖,作者認(rèn)為可以在對象初始化的時候去判斷慌植,然后用枚舉的方式進行判斷弓叛,來防止每次查找的過程璃弄,提高開發(fā)效率哥捕,我當(dāng)時思考的問題是牧抽,應(yīng)該是第一次調(diào)用方法之后就存儲在了類的方法緩存列表中,之后每次調(diào)用responseToSelector()方法都會優(yōu)先在緩存列表中查找么遥赚。當(dāng)時認(rèn)為每次查找的算法復(fù)雜度O(1)的扬舒,對于程序的優(yōu)化沒有必要,而且如果代理方法很多的情況下凫佛,對象初始化也會增加程序的負(fù)荷讲坎,因為這個時候會把所有的方法加入到緩存列表,這樣的方案本身意義不大愧薛。然后我就去看runtime的源碼驗證我的想法晨炕,現(xiàn)在我們一起來看一下我們是不是有必要做這一塊的優(yōu)化。
1.responseToSelector() vs 作者的方案
日常使用方案
作者的方案用OC的方案寫出來是下邊的樣子
對比兩種方法我們很容易明白作者的意圖是在程序每次創(chuàng)建的時候就先去我們的代理類中去讀取當(dāng)前代理類有沒有實現(xiàn)當(dāng)前的代理方法毫炉,然后創(chuàng)建一個狀態(tài)枚舉動態(tài)的存儲類的返回結(jié)果瓮栗,每次判斷方法可否執(zhí)行前只要進行一次簡單的位運算即可。這時我發(fā)現(xiàn)即使responseToObject()方法即便是O(1)操作瞄勾,在時間級的優(yōu)化上作者的方案顯然也是更好的费奸。但是作者的方案同樣有一些需要注意的問題。
1)假如說你的程序在收到一些服務(wù)器的返回結(jié)果之后用runtime添加代理方法进陡,而這個時候我們已經(jīng)創(chuàng)建了代理愿阐,那么這個時候是無法執(zhí)行代理方法的。所以這時還要再有動態(tài)的改變枚舉狀態(tài)的新的接口趾疚,此時程序的復(fù)雜性就會上升缨历。這個時候我覺得還是使用responseToSelect()方法更好一些以蕴。而且很少有程序會遇到這樣的情況,所以這一點只有遇到這種情況的時候注意即可辛孵。
2)在我們的程序中舒裤,可能會有多個對象成為一個協(xié)議的代理,我們不可能每次在協(xié)議創(chuàng)建的時候觉吭,再去專門的寫一個類來記錄狀態(tài)那樣程序的耦合性無疑有上升了腾供,而且也沒有意義,所以這個時候鲜滩,我們每一個協(xié)議都要跟隨一個類來做綁定伴鳖,用于記錄代理對象是不是可以執(zhí)行某個協(xié)議方法,即便是協(xié)議的繼承徙硅,一樣要創(chuàng)建多個相應(yīng)的綁定類榜聂。這樣雖然稍有麻煩,但是我覺得這一部分還是合理的嗓蘑。
3)如果代理方法很多的情況下须肆,在初始化階段無疑就增加了對應(yīng)的綁定的類的負(fù)荷,因為需要在初始化的時候去方法列表中讀取所有的方法桩皿,在存儲到方法緩存列表中去還是需要一些耗時的豌汇,所以這時其實需要做時間上的衡量之后進行選擇的。如果協(xié)議多次調(diào)用泄隔,方法少拒贱,那么作者的方案更好。如果協(xié)議的每個方法調(diào)用次數(shù)不多佛嬉,且協(xié)議的方法還很多的情況下逻澳,我認(rèn)為responseToSelect()方法應(yīng)該更好一些。
4)想到上邊的3個問題之后暖呕,我就思考是不是有更好的處理方案呢斜做,將綁定的類的負(fù)荷拆分到每一次方法的調(diào)用階段,即便出現(xiàn)方法一的情況湾揽,也不需要在協(xié)議的綁定類中添加狀態(tài)改變的操作瓤逼。也不用再去考慮調(diào)用次數(shù)和方法數(shù)量之間的抉擇。我認(rèn)為我們可以將綁定關(guān)系的類進行一下修改即可钝腺。綁定類中的代碼修改如下抛姑。
經(jīng)過上邊的優(yōu)化后我們在第一次調(diào)用的時候會相應(yīng)responseToSelector()方法之后赞厕,如果響應(yīng)到了之后都是根據(jù)枚舉狀態(tài)去相應(yīng)艳狐。也沒有了綁定類一開始加載很多方法時會產(chǎn)生的高負(fù)荷,這樣的代碼看起來很好皿桑,但是對于那些占內(nèi)存很大的程序毫目,我覺得為了保護寶貴的內(nèi)存蔬啡,不去一次次創(chuàng)建這個雖然很小的綁定類。還是使用的系統(tǒng)的方案镀虐。因為我認(rèn)為系統(tǒng)的方案本身也是很好的(下文會有介紹),當(dāng)然這樣的方法對于程序的性能還是有優(yōu)化的刮便。與本文相關(guān)的demo下載地址。
當(dāng)然看了我的思路之后恨旱,有更好的解決方案,煩請您給我留言搜贤,我會在本文中進行補充谆沃,大家一起成長~
說完了上邊的優(yōu)化,我們回頭再來看一下responseToSelector()到底在底層做了什么仪芒,它的運行效率到底是怎么樣的呢?
2)responseToSelector()的執(zhí)行效率怎么樣据沈?
這個時候我做的第一件事情就是去查看相關(guān)的源碼,起初先到objc-class.mm文件中去調(diào)用下邊的兩個方法饺蔑。 如果sel傳入空的或者類被釋放的話卓舵,其實直接就返回了NO.?
通過一系列的查詢之后,會發(fā)現(xiàn)程序被定位到了下邊的代碼處膀钠,這個方法在objc-runtime-new.mm文件中可以找到
查看了上邊的代碼的源代碼,我知道程序之后的調(diào)用方式其實就是先從緩存池中進行查找融击,沒有的情況下會從方法列表中取等一系列的操作雳窟。這篇文章可以看到lookupImporForward的實現(xiàn)的一些大體上的代碼,有比較詳細(xì)的注釋封救。這時我發(fā)現(xiàn)其實從緩存中讀取主要就是調(diào)用了 ?_cache_getImp(cls, sel) 這個方法,但是我從源碼中沒有找到這個方法誉结,后來在網(wǎng)上查到了美團技術(shù)團隊對于這一塊的解釋,才知道這一部分的實現(xiàn)使用匯編語言掉盅。但是讀取美團的原文加上我從國外網(wǎng)站上看到的文章,我才知道原來在方法列表的緩存列表中讀取到已執(zhí)行過的方法在算法的復(fù)雜度上并不是O(1)的趾痘。但是那一塊的執(zhí)行都是匯編實現(xiàn),所以執(zhí)行效率還是比較快的卵贱,所以系統(tǒng)對于這一塊的執(zhí)行速度還是很有保證的侣集,但是速度上肯定比不上一次位運算。
所以在具體的場景下肚吏,我們追求極致的用戶體驗的時候,這樣的方式真的值得我們思考和引用党觅。
如果文章中有任何問題斋泄,希望您不吝指出杯瞻,我一定會及時進行修復(fù)炫掐。