javascript
【腾讯Bugly干货分享】JSPatch 成长之路
本文來自于騰訊bugly開發(fā)者社區(qū),非經(jīng)作者同意,請(qǐng)勿轉(zhuǎn)載,原文地址:http://dev.qq.com/topic/579ef...
Dev Club 是一個(gè)交流移動(dòng)開發(fā)技術(shù),結(jié)交朋友,擴(kuò)展人脈的社群,成員都是經(jīng)過審核的移動(dòng)開發(fā)工程師。每周都會(huì)舉行嘉賓分享,話題討論等活動(dòng)。
本期,我們邀請(qǐng)了騰訊WXG iOS開發(fā)工程師——bang 陳振焯,為大家分享《JSPatch成長之路》。
如何加入 Dev Club?
移動(dòng)端開發(fā)經(jīng)驗(yàn) >= 2 年,微信掃描下方群管理微信二維碼,備注姓名-公司(或產(chǎn)品) 申請(qǐng)加入。
分享內(nèi)容簡介:
JSPatch 是 iOS 上的動(dòng)態(tài)更新框架,只需要引入小小的引擎文件,就可以用 JS 調(diào)用和替換任意 OC 方法。目前被普遍用于實(shí)時(shí)修復(fù) bug,已有超過2500個(gè) APP 接入,本次分享介紹 JSPatch 發(fā)展過程中遇到的問題和解決思路。
(此內(nèi)容已在 GMTC 線下分享過,本次重新整理為線上分享)
內(nèi)容大體框架:
起步:介紹 JSPatch 的誕生和當(dāng)時(shí)碰到的難題
發(fā)展:介紹 JSPatch 如何補(bǔ)全周邊功能變得更好用
下一步:介紹 JSPatch 下一步的計(jì)劃
分享人介紹:
bang 陳振焯 廣州研發(fā)部 目前負(fù)責(zé)開發(fā)微信讀書iOS端,博客 http://blog.cnbang.net
下面是本期分享內(nèi)容整理
大家好,我是 bang,目前在廣州研發(fā)部做微信讀書 iOS 端,今天分享的主題是《JSPatch 成長之路》。
我在去年5月發(fā)布了 JSPatch (https://github.com/bang590/JS...?這個(gè)開源庫,現(xiàn)在廣泛應(yīng)用于 iOS 的熱修復(fù),今天分享一下 JSPatch 過去一年以來的成長。
分享共分為三個(gè)部分:
起步 —— 介紹JSPatch的誕生和當(dāng)時(shí)碰到的難題
發(fā)展 —— 介紹JSPatch如何補(bǔ)全周邊功能變得越來越好用
下一步 —— 介紹JSPatch下一步的計(jì)劃
一、起步
先說下起步階段。當(dāng)時(shí)碰到的一個(gè)問題是:APP 線上 bug 修復(fù)周期長,成本高,版本發(fā)布出去后,發(fā)現(xiàn)一個(gè) bug,要修復(fù)這個(gè) bug 就必須得另外發(fā)一個(gè)版本,也就是要經(jīng)歷:測試——打包——發(fā)布——審核——用戶下載,這一系列過程,成本非常高,最后還很難讓所有用戶都升級(jí)上來。
當(dāng)時(shí)業(yè)界已有一個(gè)解決方案,叫 waxPatch,它是在APP里嵌入 lua 引擎,然后通過 OC 的 runtime 接口在 lua 里調(diào)用和替換 OC 方法,這樣就可以下發(fā) lua 腳本替換原生代碼,動(dòng)態(tài)修復(fù) bug。
waxPatch: https://github.com/mmin18/Wax...
這是個(gè)不錯(cuò)的方案,但當(dāng)時(shí)的 waxPatch 存在很多缺陷:
首先是 wax 已經(jīng)多年不維護(hù)了,導(dǎo)致不支持一些 block/64 位等新特性,此外當(dāng)時(shí) wax 還有文檔不足,測試不足,線程不安全,難以調(diào)試等坑。
于是開始探求更好的解決方案。很容易想到如果用 JavaScript 做這個(gè)事情的話,相對(duì) lua 原生就有一些優(yōu)勢:
iOS 里已內(nèi)置 JavascriptCore 引擎,無需再另外嵌入。
JS 在終端應(yīng)用廣泛,很多混合開發(fā)內(nèi)嵌 H5 頁面就是用 JS。
符合蘋果審核規(guī)則,蘋果在文檔里說明不可以下載可執(zhí)行的代碼,由 JavascriptCore 執(zhí)行的除外。
那么有沒有人試過這樣做呢?用 Javascript 調(diào)用和替換 OC 方法,當(dāng)然是有的。
- 在當(dāng)時(shí)有一個(gè)開源庫 JavascriptBridge (https://github.com/kishikawak..., 它可以用 JS 調(diào)用 OC 接口。?不過它用的是 JavascriptCore 原生的接口做的,需要事先在 OC 里定義好要調(diào)用的接口,沒有事先定義的不能調(diào),這導(dǎo)致它的實(shí)現(xiàn)很臃腫,因?yàn)橐?OC 定義大量的方法。此外它也不能替換 OC 方法,實(shí)用性很低。 
- 當(dāng)時(shí)國外還有一個(gè)熱補(bǔ)丁服務(wù)叫 rollout (http://rollout.io)。 它是一個(gè)服務(wù)平臺(tái),底層也是用 JS 調(diào)用和替換 OC 方法去實(shí)時(shí)修復(fù) bug,不過它不是開源的,只能在這個(gè)平臺(tái)上用,另外它的 JS 寫法是比較復(fù)雜的,看看這個(gè)例子就知道,這導(dǎo)致它不得不在平臺(tái)上做一些便捷的功能,把一些常用的操作封裝起來,減少使用者寫代碼。 
總的來說,當(dāng)時(shí)并沒有一個(gè)更好的方案,于是想自己造個(gè)。
當(dāng)時(shí)期望做到的效果是這樣的:
我在 JS 寫 UIView.allOC(), 然后傳給 JavascriptCore 執(zhí)行,JavascriptCore 把我要調(diào)用的信息,這里類名是 UIView,類方法名是 alloc 傳遞給 OC,OC runtime 就可以找到這個(gè)類和方法進(jìn)行調(diào)用。這是最基本的一個(gè)語句調(diào)用。
實(shí)際上當(dāng)時(shí)實(shí)現(xiàn)這個(gè)最基本的調(diào)用就遇到一個(gè)檻,在 JS 里這條語句根本無法執(zhí)行:
要讓這條語句在 JS 環(huán)境中可以執(zhí)行,在 JS 的語言規(guī)則下,UIView 必須是一個(gè)對(duì)象,alloc 必須這個(gè)對(duì)象的一個(gè)方法。
也就是說要像這樣定義后才可以執(zhí)行:
UIView 必須是一個(gè)對(duì)象這點(diǎn)沒問題,在調(diào)用前定義就可以,但 UIView 的方法必須在調(diào)用前定義就很糟糕,這意味著如果你想調(diào)用任意 UIView 的方法,你就需要提前把所有 UIView 的方法都找出來,每一個(gè)方法都要預(yù)先定義好。
也就是說在使用UIView之前,需要先去 OC 把UIView所有方法找出來,然后構(gòu)建UIView對(duì)象,每個(gè)方法都在這個(gè)對(duì)象里生成對(duì)應(yīng)的函數(shù),然后你才可以調(diào)用UIView的任意方法。
JSPatch 在開發(fā)時(shí)就嘗試過這種方案,后來發(fā)現(xiàn)這些對(duì)象的方法太多了,僅 NSObject 基類的實(shí)例方法就有830個(gè),類方法有118個(gè),這導(dǎo)致在JS生成的對(duì)應(yīng)的對(duì)象占用內(nèi)存極高,NSObject就占了1.3M,UIView占2M。這根本不可用。
對(duì)此我還進(jìn)行了一些優(yōu)化嘗試,例如去除掉里面的下劃線開頭的私有方法,在 JS 構(gòu)造繼承鏈共用基類方法。但這些優(yōu)化都沒多少效果,占用內(nèi)存依舊很高。當(dāng)時(shí)就覺得不太可能實(shí)現(xiàn)。
實(shí)際上當(dāng)時(shí)我陷入了一個(gè)思維定勢,做終端久了,思維停留在 iOS 的 OC 世界,寫代碼必須遵守語言的規(guī)則,上述的困難也是在遵守 JS 語言規(guī)則這個(gè)前提下碰到的。
如果有方法不遵守語言規(guī)則呢?實(shí)際上在 JS 界,有個(gè)很常用的伎倆,就是預(yù)編譯:
也就是我們寫的腳本不直接拿給 JS 引擎執(zhí)行,而是進(jìn)行一些轉(zhuǎn)換后才執(zhí)行,在現(xiàn)代框架這個(gè)用法很常見, react/vue 都用了,甚至還有像 coffieScript 這樣把 JS 完全換成另一種語言的做法。
想到這一點(diǎn),剛才的問題就很好解決了。
只需要把所有函數(shù)調(diào)用在執(zhí)行前都替換一遍,變成去調(diào)用一個(gè)固定的 __c 函數(shù), 這個(gè) __c 函數(shù)模擬了 ruby/lua 等語言的元方法機(jī)制,對(duì)方法調(diào)用進(jìn)行轉(zhuǎn)發(fā)。
還是以調(diào)用 UIView 的 alloc 方法為例,這個(gè)語句在執(zhí)行之前會(huì)被替換為調(diào) UIView 的 __c 方法,把 "alloc" 作為字符串參數(shù)穿進(jìn)去,在 __c 方法里判斷調(diào)用者 UIView 是不是 OC 對(duì)象。如果是,就把類名和傳進(jìn)來的方法名傳到 OC 層進(jìn)行調(diào)用,如果不是,就調(diào)用回 JS 這個(gè)對(duì)象的方法。
這樣做簡潔高效地解決了前面的問題,不需要去 OC 遍歷每個(gè)類的方法,不需要存儲(chǔ)這些方法,就可以調(diào)用任意 OC 方法,只需要給 JS 基類定義一個(gè) __c 方法就可以了。正則替換后無論調(diào)用 OC 的什么方法,都不會(huì)有語法錯(cuò)誤,因?yàn)槎甲兂烧{(diào)用這個(gè) __c 方法,在這個(gè) __c 方法里做處理去 OC 層調(diào)用相應(yīng)的方法就行了。
使用這種方案后內(nèi)存的占用下降了 99%,甚至更多,也使 JSPatch 的使用成為可能,這是 JSPatch 最核心的一點(diǎn)。
解決這個(gè)核心問題后,后面就是細(xì)化功能了,JSPatch 發(fā)布以后一直在完善,包括最基本的調(diào)用和替換OC方法,還有支持64位,支持 block,支持包括 c指針/Class/結(jié)構(gòu)體等類型,支持 c函數(shù)的調(diào)用等,這里面的細(xì)節(jié)原理我覺得看文章會(huì)比較清晰,相關(guān)文章都可以在 github wiki (https://github.com/bang590/JS...?上找到,這里就不多說了。
二、發(fā)展
接下來說說 JSPatch 是怎樣進(jìn)行進(jìn)一步發(fā)展的。
在完善 JSPatch 的同時(shí),我也在想,如何把 hotfix 做得更好。
主要有兩個(gè)思路:
降低使用門檻
提高使用效率
我們一個(gè)點(diǎn)一個(gè)點(diǎn)來看對(duì)這兩個(gè)問題是怎么做的。
首先 JSPatch 在易用性上一直堅(jiān)持著一個(gè)理念,就是 keep it simple and tiny,用中文說就是保持精巧,保持好用。JSPatch 從開源到現(xiàn)在一年多,增加和完善了很多功能,但它的使用方式和接口都沒有變過,一直以來都是只有三個(gè)文件,拖入項(xiàng)目直接可以使用,也會(huì)很謹(jǐn)慎地新增接口,不會(huì)影響到舊接口的使用,不會(huì)出現(xiàn)同一份代碼在舊版本能用,在新版本不能用的情況,在易用性上降低使用門檻。
另一個(gè)問題是安全問題。
JSPatch 可以調(diào)用和替換任意 OC 方法,權(quán)限很大,如果在傳輸過程中被第三方攻擊,替換了下發(fā)的代碼去執(zhí)行,會(huì)對(duì)用戶和 APP 本身造成很大傷害。如果每個(gè)接入 JSPatch 的人都要考慮這個(gè)安全問題,接入門檻就會(huì)很高,也可能會(huì)因?yàn)榭紤]不周全導(dǎo)致 APP 處于危險(xiǎn)狀態(tài)。
對(duì)此當(dāng)時(shí)詳細(xì)考慮了安全策略。對(duì)這種情況:
- 最簡單的方案是直接對(duì)腳本加密,后臺(tái)使用固定密鑰加密腳本后下發(fā),客戶端使用同樣的密鑰解密。這種方案的優(yōu)點(diǎn)是簡單,缺點(diǎn)是這個(gè)密鑰必須存在客戶端,黑客很容易破解拿到這個(gè)密鑰,然后通過傳輸過程第三方攻擊,下發(fā)同樣用這個(gè)密鑰加密的惡意代碼,就沒有安全可言了。 
- 第二個(gè)方案是讓腳本通過 https 傳輸,這個(gè)方案的優(yōu)點(diǎn)是安全性高,只要客戶端對(duì)證書進(jìn)行過足夠的驗(yàn)證,就能很好地保證安全性。缺點(diǎn)是這個(gè)方案門檻高,部署繁瑣,需要購買證書,對(duì)一些中小 APP 來說可能難以接受,并且如果用戶手機(jī)信任了一些惡意證書,也還是存在被攻擊的危險(xiǎn)。 
- 第三個(gè)方案是使用RSA簽名驗(yàn)證。 
 整個(gè)流程是這樣:
第一步服務(wù)端計(jì)算腳本文件的MD5值,用存在服務(wù)端的私鑰對(duì)這個(gè)MD5值進(jìn)行加密,然后把這個(gè)MD5值和腳本一起打包下發(fā)給客戶端。客戶端拿到腳本和加密后的MD5值,用存在客戶端的公鑰進(jìn)行解密,拿到服務(wù)端計(jì)算的MD5值,本地再計(jì)算一遍腳本文件的MD5值,對(duì)比這兩個(gè)值是否一致,若一致則表示傳輸過程中沒有被篡改。
如果第三方要截獲請(qǐng)求下發(fā)惡意腳本,第三方必須用私鑰加密這個(gè)惡意腳本的MD5值一起下發(fā),才能通過驗(yàn)證執(zhí)行,只要服務(wù)端不被攻破,第三方就沒有私鑰,也就無法進(jìn)行篡改。
可以看到這第三個(gè)方案門檻低,通用性高,部署簡單,安全性也高,對(duì)服務(wù)端和客戶端都沒有什么特殊要求。
我把這一套安全方案做成一個(gè)組件,叫 JPLoader,也開源在 JSPatch 項(xiàng)目上(https://github.com/bang590/JS..., 需要部署 JSPatch 的同學(xué)可以直接使用這套組件,解決安全性問題,客戶端的工作就完成了,只剩下后端的工作。
前面把安全性問題解決了,只剩下后端的工作,但搭建后臺(tái)對(duì)使用者來說也是挺麻煩的事,特別是作為 iOS 開發(fā)者,在中小公司自己搭后臺(tái)麻煩,在大公司要后臺(tái)幫你搭也不容易,這又會(huì)導(dǎo)致使用 JSPatch 的門檻還是很高。
于是在想這部分工作能不能也幫使用者省了呢?
對(duì)此我搭建了 JSPatch 平臺(tái) (http://JSPatch.com), 讓使用 JSPatch 的人不需要搭建后臺(tái),直接通過平臺(tái)下發(fā)補(bǔ)丁代碼。
這個(gè)平臺(tái)幾個(gè)月前已經(jīng)開放注冊(cè),現(xiàn)在所有人都可以使用。
在搭建這個(gè)平臺(tái)時(shí),碰到一個(gè)問題值得分享一下,就是如何支持高并發(fā)?
由于 JSPatch 的補(bǔ)丁特性,補(bǔ)丁需要及時(shí)推送給用戶,也就是說至少需要在每次啟動(dòng)時(shí)向服務(wù)端請(qǐng)求詢問 APP 是否有新的補(bǔ)丁,有的話下發(fā)執(zhí)行。這里詢問的請(qǐng)求量是很高的,單個(gè) APP 可以控制,但平臺(tái)要面對(duì)多個(gè) APP,累計(jì)起來的請(qǐng)求數(shù)量會(huì)非常多,并發(fā)會(huì)很高,怎樣支撐這樣的高并發(fā)?
正常來說這樣一個(gè)系統(tǒng)整體設(shè)計(jì)大致是這樣的:
平臺(tái)用戶把腳本放到平臺(tái)服務(wù)端,服務(wù)端的數(shù)據(jù)庫保存著腳本的各種信息和內(nèi)容,APP 客戶端向平臺(tái)發(fā)起請(qǐng)求詢問是否有新腳本,平臺(tái)服務(wù)端接收到請(qǐng)求后通過 CGI 處理請(qǐng)求參數(shù),根據(jù) APPkey 等參數(shù)從數(shù)據(jù)庫拿出這個(gè) APP的信息,然后組裝數(shù)據(jù)告訴APP客戶端有沒有新腳本。
這里的詢問請(qǐng)求至少時(shí) APP 每次啟動(dòng)都要發(fā)一次請(qǐng)求,才能保證腳本的更新能盡快下發(fā)。請(qǐng)求量大時(shí),這里從數(shù)據(jù)庫取出數(shù)據(jù)很容易成為整個(gè)系統(tǒng)的瓶頸,CGI 處理請(qǐng)求參數(shù)和組裝數(shù)據(jù)也要耗不少資源。
對(duì)此我改用了另一種方式:
平臺(tái)用戶上傳腳本到平臺(tái)服務(wù)器時(shí),服務(wù)端除了把 APP 信息存在 DB 外,同時(shí)會(huì)另外上傳一份 JSON 靜態(tài)文件到靜態(tài)云服務(wù)器,JSON 里保存了當(dāng)前補(bǔ)丁的版本,而這個(gè)靜態(tài)資源的文件名是由 APPkey/APP 版本號(hào)組成的。
例如這里腳本補(bǔ)丁版本號(hào)是10,這個(gè)JSON靜態(tài)文件的內(nèi)容就是 {v:10}。可以想象靜態(tài)文件的訪問路徑就是:
http://JSPatch.com/{APPkey}/{APP_version}.json然后 APP 客戶端不再向平臺(tái)服務(wù)端發(fā)請(qǐng)求,而是向這個(gè)靜態(tài)資源服務(wù)器發(fā)請(qǐng)求,根據(jù) APPKey 和 APP 版本直接請(qǐng)求到這個(gè) JSON 文件,里面帶的版本號(hào)信息就可以告訴 APP 腳本是否有更新。
整個(gè)流程就變成了:
APP 向靜態(tài)服務(wù)器詢問是否有新補(bǔ)丁,靜態(tài)服務(wù)器直接返回預(yù)先設(shè)置好的 JSON,就結(jié)束了。
這樣 APP 永遠(yuǎn)不會(huì)跟平臺(tái)服務(wù)器打交道,只需跟靜態(tài)資源服務(wù)器打交道,靜態(tài)資源的高并發(fā)處理起來就簡單得多,成本也低很多,現(xiàn)在有很多靜態(tài)資源云存儲(chǔ),直接接入就可以了,以這些云存儲(chǔ)的能力,支持多高的并發(fā)都沒有問題,用戶量多大的 APP 接入都可以支撐到。就是這樣 JSPatch 平臺(tái)解決了高并發(fā)問題,可以投入使用。
接下來在開發(fā)效率上,有一個(gè)問題是轉(zhuǎn)換代碼效率低。
我們用 JSPatch 修復(fù) bug 時(shí)時(shí)以方法為單位進(jìn)行替換的,若原方法有上百行,你的需求只是修改其中一兩行代碼,你也要把這上百行代碼人工翻譯成 JS 才行。對(duì)此我開發(fā)了JSPatch Convertor 這個(gè)工具,可以自動(dòng)把 OC 代碼轉(zhuǎn)為 JSPatch 代碼,提升開發(fā)效率。
這個(gè)工具也開源在 github 上(https://github.com/bang590/JS..., 支持了大部分語法特性,但目前還做不到支持所有特性,像私有變量/靜態(tài)變量/宏這些還不支持,所以轉(zhuǎn)換后需要人工修改,但還是很大地提高了使用 JSPatch 的效率。
總結(jié)下來,在降低使用門檻上,JSPatch 保證了易用性,封裝了安全方案,提供了 JSPatch 平臺(tái)讓使用者可以直接接入,另外還有完善的文檔和解析文章保證使用無障礙。提高使用效率上,做了 JSPatch Convertor 自動(dòng)轉(zhuǎn)換代碼,也內(nèi)置了一些擴(kuò)展方便直接調(diào)用一些常用的 C函數(shù)。
經(jīng)過不斷發(fā)展,JSPatch 可以說是 iOS hotfix 的最佳解決方案。
目前大部分應(yīng)用都已經(jīng)接入使用,據(jù)不完全統(tǒng)計(jì)至少有 2500 個(gè) APP 接入,經(jīng)過了的大用戶量的考驗(yàn)。
三、下一步
接下來說說下一步的計(jì)劃,JSPatch 在 hotfix 上已經(jīng)做得不錯(cuò),目前下一步打算推動(dòng)使用 JSPatch 開發(fā)功能模塊。
JSPatch 做這個(gè)事情跟 React Native 和 weex 這類方案比起來,會(huì)有一些優(yōu)勢:
- 首先 React Native 和 weex 都是從前端出發(fā)擴(kuò)展到終端,是前端方案的延伸,他們的體系對(duì)于前端來說更熟悉,對(duì)于終端來說,意味著要重新學(xué)習(xí)前端的一套知識(shí),學(xué)習(xí)成本較高,而 JSPatch 是從終端出發(fā),編碼體系也差不多是直譯 OC,學(xué)習(xí)成本較低。 
- 第二點(diǎn)是 ReactNative 和 Weex 是比較大型的框架,環(huán)境配置都很復(fù)雜,也會(huì)增大不少安裝包的大小,如果說只想擴(kuò)展實(shí)現(xiàn)一兩個(gè)小功能,接入這么大型的框架不合適。而 JSPatch 前面也說了,屬于微型框架,只有三個(gè)文件,也無需環(huán)境配置。 
- 第三點(diǎn)是 ReactNative 和 Weex 的組件都是要一個(gè)個(gè)封裝好,難以復(fù)用現(xiàn)有的 OC 組件,并且他們都是大型框架,在未成熟階段框架本身實(shí)現(xiàn)上的坑會(huì)很多,而 JSPatch 可以直接復(fù)用所有 OC 現(xiàn)有組件,并且只是薄薄的轉(zhuǎn)接層,坑會(huì)較少。 
但 JSPatch 要用于開發(fā)功能,有兩個(gè)問題:
開發(fā)效率較低
運(yùn)行效率較低。
在開發(fā)效率上,我做了兩件事去提高,第一個(gè)是 JSPatchX 代碼補(bǔ)全插件 (https://github.com/bang590/JS...。
寫 JSPatch 代碼時(shí)并不像 OC 那樣有代碼補(bǔ)全,在調(diào)用 OC 長長的方法時(shí)效率會(huì)很低,而且用 JSPatch 寫功能時(shí),不像 hotfix 那樣有對(duì)應(yīng)的 OC 代碼,也無法使用前面說的 JSPatchConvertor 進(jìn)行轉(zhuǎn)換。于是做了 JSPatchX 去彌補(bǔ)這個(gè)缺陷,可以在 XCode 自動(dòng)提示補(bǔ)全 JSPatch 代碼。
另一個(gè)是 Playground 即時(shí)刷新范例 (https://github.com/bang590/JS...
可以實(shí)時(shí)預(yù)覽 JSPatch 腳本執(zhí)行的結(jié)果,無需像原生代碼那樣每一次修改都要 build 重啟才能看到效果,這也是腳本語言的優(yōu)勢。使用者可以仿照這個(gè) playground 的實(shí)現(xiàn),在開發(fā)功能時(shí)在自己的頁面實(shí)現(xiàn)這樣的即時(shí)刷新,這樣一定程度上提高了開發(fā)效率。
接下來看看運(yùn)行效率。
JSPatch 寫功能時(shí)運(yùn)行效率低,于是著手進(jìn)行優(yōu)化,第一步是確定瓶頸,發(fā)現(xiàn)運(yùn)行速度最慢的在于在 JS 調(diào)用 JS 上定義的新方法。
例如這里新定義了一個(gè)dribbbleView類,里面有個(gè)新方法renderItem,在 JS 里調(diào)用這個(gè)新方法時(shí),速度很慢。
分析下這個(gè)調(diào)用過程:
主要問題在于這個(gè)新定義的方法與 OC 掛鉤,這一次普通的調(diào)用,需要在 JS 和 OC 之間不斷來回通信,不斷進(jìn)行參數(shù)轉(zhuǎn)換,經(jīng)過這9個(gè)步驟后才能成功調(diào)用。
對(duì)此我通過一些手段做了優(yōu)化,把這樣的方法直接放在 JS 環(huán)境上,在 JS 調(diào)用這個(gè)方法時(shí)無需再與 OC 通信,整個(gè)調(diào)用流程就變成了只有兩步:
經(jīng)過這個(gè)優(yōu)化后,這樣的方法調(diào)用性能最高提高 700 倍,這才使 JSPatch 寫功能變成一件靠譜的事。
除此之外還做了一些其他優(yōu)化,包括提升新增 property 性能,提供跟定義 OC 類一樣的純 JS 類定義接口,自動(dòng)轉(zhuǎn)換參數(shù)類型等,具體優(yōu)化細(xì)節(jié)可以在這篇文章(http://blog.cnbang.net/tech/3... 上看到。
我用 JSPatch 寫了個(gè) Dribbble 客戶端 demo (https://github.com/bang590/JS...? 在 iPhone5C 上測試過,滑動(dòng)性能沒有問題。
最后,可以從這個(gè)腦圖看出 JSPatch 的現(xiàn)狀,周邊設(shè)施仍在繼續(xù)建設(shè)中。
我今天的分享就到這里,謝謝。
問答環(huán)節(jié):
Q1: JSPatch 的底層原理跟 ReactNative 是不是差不多呢?有受到其啟發(fā)么?
JSPatch 的原理跟 ReactNative 是完全不一樣的,JSPatch 是 OC 方法調(diào)用和替換的一層轉(zhuǎn)接,ReactNative 并不會(huì)去調(diào)用和替換 OC 方法,它有自己的一套通信規(guī)則。
Q2: 本身基于OC runtime 對(duì) Swift 的項(xiàng)目如何支持?
Swift 相關(guān)問題在 wiki 里有提到:?
只支持調(diào)用繼承自 NSObject 的 Swift 類
繼承自 NSObject 的 Swift 類,其繼承自父類的方法和屬性可以在 JS 調(diào)用,其他自定義方法和屬性同樣需要加 dynamic 關(guān)鍵字才行。
若方法的參數(shù)/屬性類型為 Swift 特有(如 Character / Tuple),則此方法和屬性無法通過 JS 調(diào)用。
Swift 項(xiàng)目在 JSPatch 新增類與 OC 無異,可以正常使用。
Swift 的原生類目前沒找到替換的方法,動(dòng)態(tài)調(diào)用倒是可以實(shí)現(xiàn)。
Q3: JSPatch 運(yùn)行一次就會(huì)把JS轉(zhuǎn)換為 OC 緩存起來?那我們可以利用它去做一些重復(fù)調(diào)用的事情?甚至用來開發(fā)?它的效率和原生相近吧?
會(huì)緩存一些 methodSignature,但還是得通過反射 (className->class->imp) 去找到要調(diào)用的方法,效率會(huì)比原生低。但一般程序的瓶頸不會(huì)在語言這里。
Q4: 對(duì)于 JSPatch 資源更新服務(wù)平臺(tái)還是表示一些擔(dān)憂,如果被別人攻破了,豈不是很多 APP 都受牽連了?
JSPatch 平臺(tái)就算平臺(tái)被人黑了,也無法對(duì)平臺(tái)上的 APP 下發(fā)惡意代碼。只要使用者用了自定義的 RSA 密鑰就可以了,只有使用者有私鑰,每次發(fā)布腳本都要使用這個(gè)私鑰,平臺(tái)不會(huì)保存它,詳情可見:http://JSPatch.com/DOCs/rsa
Q5: 現(xiàn)在 iOS 加快了審核速度,好像現(xiàn)在是24小時(shí)內(nèi)審核上線。那現(xiàn)在 JSPatch 前景還會(huì)好么?
審核只是一個(gè)環(huán)節(jié),測試/打包/發(fā)布/用戶下載,這些其他環(huán)節(jié)還是不可少,并且最大的問題還是是用戶下載更新不可控。
Q6: Swift 屬于靜態(tài)編譯類型,是不是可以利用類似 c函數(shù)替換的方法呢?像 fishhook 這樣的工具
fishhook 需要編譯時(shí)確定要替換的函數(shù)指針,并不能在運(yùn)行時(shí)替換任意 c函數(shù)
Q7: 我看網(wǎng)上的一些介紹說 JSPatch 對(duì)小的 bug 修復(fù)好點(diǎn),大的還是提交新的版本,但是我看您介紹使用靜態(tài)資源服務(wù)器管理.應(yīng)該不存在數(shù)據(jù)量大,并發(fā)的問題.這個(gè)您怎么看?
他指的大的 bug 應(yīng)該是要寫很多代碼才能修復(fù)的 bug 吧?這點(diǎn)應(yīng)該跟 JSPatch 開發(fā)效率問題有關(guān),對(duì)于大量的代碼他不想原生 OC 寫一套修復(fù),再用 JSPatch 寫一套,跟數(shù)據(jù)量和并發(fā)應(yīng)該沒什么關(guān)系。
Q8: 為何 JSPatch 上面,QQmail 沒有接入?有什么顧慮嗎?
因?yàn)镼Q郵箱在 JSPatch 出現(xiàn)之前已接入 lua,剛出現(xiàn)時(shí) JSPatch 還不是很成熟,團(tuán)隊(duì)當(dāng)時(shí)想同時(shí)使用兩種方案作對(duì)比,時(shí)間久了也沒有再切換過來了。
Q9: 有沒有可能進(jìn)一步提升 JSPatch convertor 的能力。最終發(fā)到直接打開 Xcode 項(xiàng)目,尋找依賴,通過語法語義分析等,將 OC 轉(zhuǎn)換為 JS
可以做到的,不過這事要投入很大精力,之前有搞過一個(gè) demo,直接用 OC 寫 Patch,然后在執(zhí)行前轉(zhuǎn)換成 JSPatch 代碼,有一個(gè)開源庫 JSTalk (https://github.com/ccgus/JStalk) 有基本的 OC->JS 的轉(zhuǎn)換,但要做到好用還有很多工作。
Q10: 請(qǐng)問如果我的 APP 引入了 JSPatch, 但是產(chǎn)生 crash 的代碼并不是通過 JS 寫的, 而是原生的 OC 代碼, 那么 JSPatch 可以通過下發(fā) JS 腳本修復(fù)這種 crash 嗎, 如果可以的話, 原理是怎樣的?
可以,原理就是把導(dǎo)致 crash 出現(xiàn)的方法替換掉,OC 調(diào)用那個(gè)方法時(shí)轉(zhuǎn)成調(diào)用 JSPatch 里寫的替換的方法,就不會(huì) crash 了
Q11: 有沒有意識(shí)到 JSPatch 的性能瓶頸最終都取決于 JavascriptCore 的性能?所以低端機(jī)永遠(yuǎn)是低性能,有沒有想過借鑒 JSX 做點(diǎn)事情呢?
JavascriptCore 的性能并沒有問題,性能瓶頸不是 JavascriptCore,目前來看瓶頸會(huì)是 OC 與 JS 通信時(shí)大對(duì)象的參數(shù)轉(zhuǎn)換,但這是可以避免的
Q12: JSPatch 效率怎么樣啊
效率可以試試上文說的 dribbbleDemo
Q13: 調(diào)試 JSPatch 時(shí)能不能打斷點(diǎn),如何定位到 JS 的 crash 堆棧
可以斷點(diǎn),文檔有寫:https://github.com/bang590/JS...
如果大家對(duì)本次分享還有問題,請(qǐng)按以下格式在DEV社區(qū)(dev.qq.com)發(fā)問答帖
發(fā)帖格式:【bang@DEV Club 你問我答】 “問題”
最后,歡迎大家關(guān)注 JSPatch 公眾號(hào):JSPatchDev,會(huì)即時(shí)推送 JSPatch 最新信息以及相關(guān)技術(shù)文章:
更多精彩內(nèi)容歡迎關(guān)注bugly的微信公眾賬號(hào):
騰訊 Bugly是一款專為移動(dòng)開發(fā)者打造的質(zhì)量監(jiān)控工具,幫助開發(fā)者快速,便捷的定位線上應(yīng)用崩潰的情況以及解決方案。智能合并功能幫助開發(fā)同學(xué)把每天上報(bào)的數(shù)千條 Crash 根據(jù)根因合并分類,每日日?qǐng)?bào)會(huì)列出影響用戶數(shù)最多的崩潰,精準(zhǔn)定位功能幫助開發(fā)同學(xué)定位到出問題的代碼行,實(shí)時(shí)上報(bào)可以在發(fā)布后快速的了解應(yīng)用的質(zhì)量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來加入我們吧!
總結(jié)
以上是生活随笔為你收集整理的【腾讯Bugly干货分享】JSPatch 成长之路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 【C#】依赖注入及Autofac
- 下一篇: slqmf刀模工具_SLQMF刀模软件下
