最近在ios群裡面看到某應用因為hotpatch稽核被拒絕, 如果hotpatch全面被封禁, 那還不如全切swift, 又能提高性能, 又能減少編碼中犯的錯誤. 仔細想想如果swift也有辦法被hotpatch, 不就更加完美了?
hotpatch是無法被全面封禁的, 可愛的程式猿們總能有應對的辦法
swift有四種方法調用方式:
inline method
static dispatch
dynamic dispatch
message send
<code>inline method</code>會在編譯期間将被調用的方法直接内聯到調用方法的方法體裡面, 這種狀況下方法調用将沒有任何開銷.
示例代碼如下:
編譯後的彙編代碼是:
可以看出彙編代碼超級簡潔, 就隻剩兩次對clock方法的調用.
<code>static dispatch</code>會通過彙編指令<code>bl</code>跳轉到被調用方法所在的位址, 位址在編譯期就已經決定. 但因為多了一次<code>bl</code>指令, 會有少量的時間消耗.
對代碼做不inline處理:
這裡比面上多出來一條<code>bl</code>指令, 跳轉到hehe1方法所在位址.
<code>dynamic dispatch</code>會生成一張類的方法表(在資料段), 在調用時通過<code>ldr</code>指令從類表取出方法所在的位址到寄存器, 再跳轉到方法所在的位址. 這種方式需要3條指令及1次記憶體通路, 所耗費的時間更長.
代碼同第一段代碼, 把<code>工程build settings的swift comipler - code generation的optimization level調為none</code>, 反編譯代碼如下:
注: 此段代碼彙編代碼經過精簡, 代碼的解釋見注釋
<code>message send</code>就是objc_msgsend的流程, 這裡暫不多做介紹.
inline的代碼明顯沒戲, 連獨立的方法都沒有了, 更沒有方法調用.
static的代碼也沒戲, 調用的方法位址在編譯期就決定好了, 無法動态的做改變(代碼在__text段, 代碼加載到記憶體後無寫權限, 無法更改).
message用oc那一套方案就可以了.
dynamic的代碼中, 是通過加載class的中繼資料中存儲的方法位址進行方法調用, 而class中繼資料位于__data段, __data段是可以讀寫的. 那不就意味着采用dynamic方式, 把class的中繼資料給篡改掉就可以patch了? 我們試試!
先來一段原始代碼:
按照正常的執行流程, 我們會在調試視窗看到:
我想把hehe1這個方法patch到另一個方法的實作(一個c方法):
從之前提到的dynamic調用方式的修改class中繼資料的思路展開說, 我們首先要擷取到aclass的class中繼資料的位址, 再擷取到hehe1方法指針在中繼資料中的偏移量, 再擷取patched_hehe1方法的指針, 再塞到class中繼資料中hehe1方法對應的位置.
開幹!
下面實作了patch_hehe1方法, 将hehe1方法給patch成patched_hehe1方法:
調用patch_hehe1方法後, aclass的hehe1方法就被patch掉了! 運作程式看調試視窗的結果:
patch成功!
在前面提到, 為了實作讓swift走dynamic dispatch, 将編譯選項中的優化級别設為了none. 那如果将優化級别恢複為<code>fast</code>後情況會變成什麼樣呢?

<code>hehe1方法被inline, patch無效, 輸出hehe1</code>
那麼問題來了! 你願意犧牲性能換取動态性麼?