天天看點

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

建立 swift 工程 <code>swiftjspatch</code>。

<code>appdelegate.swift</code>:

<code>viewcontroller</code>中設定兩個自定義屬性:<code>public</code>屬性<code>a</code>,<code>private</code>屬性<code>pa</code>:

<code>main.js</code>中去擷取這兩個自定義屬性并各自賦新值,此外為<code>viewcontroller</code>繼承自父類<code>uiviewcontroller</code>的屬性<code>title</code>設定新值:

運作結果輸出:

<code>title</code>修改成功,<code>a</code>擷取成功,<code>pa</code>通路失敗:找不到<code>selector(pa)</code>,檢視 oc 端調用堆棧:

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

js端調試:

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

經過<code>_evaluatescript:withsourceurl:</code>處理,<code>main.js</code>中的方法都被替換成<code>__c('methodname')</code>。

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

<code>defineclass</code>對js對象method的改寫也沒問題。

由以上資訊可知,jspatch 方法替換成功,方法調用環節js調用oc私有方法<code>pa()</code>也就是在<code>callselector</code>環節出錯,擷取不到方法簽名導緻後續消息轉發無法進行。<code>public</code>方法則可以成功替換實作并調用。

現在在<code>private</code>變量前聲明<code>dynamic</code>:

輸出:

變量都被成功修改,也就是說方法替換和調用都沒問題。

結論1:<code>jspatch</code>作用于繼承自<code>nsobject</code>的類,其繼承自父類的屬性/自定義<code>public</code>變量可以直接通路和修改,自定義<code>private</code>變量需要加上<code>dynamic</code>。

從上個修改屬性的案例已經看出對于繼承自<code>nsobject</code>的類的繼承自父類的方法,<code>jspatch</code>實作熱更新是沒問題的。是以直接看自定義函數的情況。

在<code>viewcontroller</code>自定義兩個函數,其中一個是<code>private</code>方法:

<code>main.js</code>中對這兩個自定義函數實作進行修改。<code>fun()</code>給a賦新值,<code>pfun()</code>給<code>pa</code>賦新值:

運作:

熱更新失敗!

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

從js調試結果看腳本是被執行過的,且「方法替換」成功,說明是oc端「方法調用」時沒有走運作時的消息轉發流程。

為兩個函數添加<code>dynamic</code>聲明:

hook成功:

swift 中靜态函數分兩種:class 函數/static 函數:

從結果看出,class 函數得到替換并調用成功,static 函數調用時沒有進行消息轉發:

建立<code>pure</code>類:

<code>main.js</code>修改<code>fun()</code>和<code>pfun()</code>的實作:

調用<code>call()</code>結果:

直接崩潰:

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

由上圖知,<code>jspatch</code>在進行到<code>overridemethod</code>進行方法實作imp替換時要求<code>class</code>實作<code>nscoping</code>協定,而不繼承自<code>nsobject</code>的swift類是不遵循該協定的,是以崩潰。

iOS熱更新解讀(三)—— JSPatch 之于 Swift繼承自 NSObject 的 Swift 類純 Swift 類Swift 原生類熱修複難點

回到崩潰代碼:

此處<code>jspatch</code>在初始化緩沖區的時候将<code>class</code>作為<code>dictionary</code>的<code>key</code>進行儲存,而<code>dictionary</code>在設定<code>key-value</code>時會拷貝 <code>key</code>值,是以會導緻給一個不遵循<code>nscoying</code>協定的對象發送了<code>copywithzone:</code>消息,導緻崩潰。

到這裡「方法替換」的步驟已經進行不下去了。<code>jspatch</code>對<code>swift</code>原生類的熱修複已經無能為力了。但<code>swift</code>熱修複的真正難點其實并不在這裡,假如我們越過<code>nscoping</code>通過某種 swift style 的方式實作了對類中方法名和對應js實作的緩存,也就是完成「方法替換」的話,熱修複就能成功了嗎?

「方法調用」才是 swift 熱修複中目前真正無解的地方,最大原因是<code>swift</code>中<code>runtime</code>相對oc中的<code>runtime</code>動态性大大減弱。

純swift類沒有動态性,但在方法、屬性前添加dynamic修飾可以獲得動态性。

繼承自nsobject的swift類,其繼承自父類的方法具有動态性,其他自定義方法、屬性需要加dynamic修飾才可以獲得動态性。

若方法的參數、屬性類型為swift特有、無法映射到objective-c的類型(如character、tuple),則此方法、屬性無法添加dynamic修飾(會編譯錯誤)

swift類在objective-c中會有子產品字首。

另外最要命的一點:<code>objc_msgsend</code>函數無法用于 swift object。這個導緻<code>jspatch</code>實作方法調用(消息轉發)的基礎機制在 swift 中失效了。

總結一下 <code>swift</code> 項目中使用<code>jspatch</code>需要注意的幾點:

隻支援調用繼承自 nsobject 的 swift 類。

繼承自 nsobject 的 swift 類,其繼承自父類的方法和屬性可以在 js 調用,其他自定義方法和屬性同樣需要加 dynamic 關鍵字才行。

若方法的參數/屬性類型為 swift 特有(如 character / tuple),則此方法和屬性無法通過 js 調用。

參考資料:

<a href="http://mp.weixin.qq.com/s?__biz=mza3odg4mdk0ng==&amp;mid=403153173&amp;idx=1&amp;sn=c631f95b28a0eb4b842a9494e43a30e5">swift runtime分析:還像oc runtime一樣嗎?</a>

<a href="https://github.com/bang590/jspatch/wiki/jspatch-%e5%9f%ba%e7%a1%80%e7%94%a8%e6%b3%95#11-swift">jspatch github wiki</a>

相關文章

<a href="https://yq.aliyun.com/articles/58873">ios 熱更新解讀(一)apatch &amp; javascriptcore</a>

<a href="https://yq.aliyun.com/articles/58874">ios 熱更新解讀(二)—— jspatch 源碼解析</a>

繼續閱讀