概述
JSPatch是什么也切?
JSPatch 是一個Github開源項目(https://github.com/bang590/JSPatch),只需要在項目里引入極小的引擎文件(項目中的JSPatch.js文件),就可以使用 JavaScript 調用任何 Objective-C 的原生接口蟹瘾,替換任意 Objective-C 原生方法。目前主要用于下發(fā) JS 腳本替換原生 Objective-C 代碼掠手,實時修復線上 bug憾朴。除了修復 bug,JSPatch也可以用于動態(tài)運營喷鸽,實時修改線上 APP 行為众雷,或動態(tài)添加功能。
JSPatch平臺是什么?
當我們使用JSPatch技術來修復線上APP的bug時砾省,需要提供JS腳本鸡岗,然后通過后臺下發(fā)這個腳本到APP本地。通常编兄,我們會把資源包放置到服務器或者七牛云存儲轩性,供APP下載。JSPatch平臺就是干了這件事翻诉,他為開發(fā)者提供一個后臺炮姨,用來托管開發(fā)者提供的JS腳本并且傳輸?shù)紸PP中。其中還提供了JS腳本版本管理的功能碰煌。
背景
在IOS開發(fā)領域舒岸,一方面由于Apple嚴格的審核標準和低效率,IOS應用的發(fā)版速度極慢芦圾,稍微大型的app發(fā)版基本上都在一個月以上蛾派。另一方面,APP由于bug修復問題產生的頻繁迭代更新同樣會給用戶群體造成困擾个少,而且開發(fā)者也不能確保用戶群體一定會更新APP洪乍。總體來說夜焦,從版本升級到APP用戶群體更新APP是一個比較漫長的等待過程壳澳,所以代碼熱修復對于IOS應用來說就顯得尤其重要。
編寫目的
本文檔旨在介紹JSPatch-動態(tài)更新IOS APP技術茫经,以及從這一技術出發(fā)巷波,學習JSPatch平臺的使用,嵌入到我們IOS項目中去卸伞。
JSPatch優(yōu)勢在哪里抹镊?
1、JS語言荤傲。目前前端開發(fā)和終端開發(fā)有融合的趨勢垮耳,作為擴展的腳本語言,JS是不二之選遂黍。
2终佛、符合Apple規(guī)則。JSPatch更符合Apple的規(guī)則雾家。iOS Developer Program License Agreement里3.3.2提到不可動態(tài)下發(fā)可執(zhí)行代碼查蓉,但通過蘋果JavaScriptCore.framework或WebKit執(zhí)行的代碼除外,JS正是通過JavaScriptCore.framework執(zhí)行的榜贴。
3豌研、小巧妹田。使用系統(tǒng)內置的JavaScriptCore.framework,無需內嵌腳本引擎鹃共,體積小巧鬼佣。
4、支持block霜浴。wax在幾年前就停止了開發(fā)和維護晶衷,不支持Objective-C里block跟Lua程序的互傳,雖然一些第三方已經實現(xiàn)block阴孟,但使用時參數(shù)上也有比較多的限制晌纫。
JSPatch優(yōu)勢在哪里?
JSPatch劣勢在于不支持iOS6永丝,因為需要引入JavaScriptCore.framework锹漱。
另外目前內存的使用上會高于wax,持續(xù)改進中慕嚷。(注:這點沒有進行驗證)
誰在使用JSPatch
目前JSPatch技術在GitHub平臺中獲得9000多顆星哥牍。廣泛應用,超過5000 個 App 已接入使用喝检。例如:微信 嗅辣、QQ、微博挠说、百度澡谭、美團、滴滴出行损俭、京東译暂、網易新聞、微信讀書撩炊。
JSPatch平臺的使用
JSPatch平臺的SDK接入
請查看平臺文檔鏈接更為詳盡:http://jspatch.com/Docs/SDK
JSPatch SDK的使用
IOS Runtime的解釋(擴展)
runtime是一套比較底層的純C語言API,屬于1個C語言庫, 包含了很多底層的C語言API崎脉。平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉成了runtime的C語言代碼,runtime算是OC的幕后工作者 比如說拧咳,下面一個創(chuàng)建對象的方法中, 舉例: OC : [[MJPerson alloc] init] runtime :objc_msgSend(objc_msgSend("MJPerson" , "alloc"),
"init")囚灼。
Runtime用來做什么骆膝?runtime是屬于OC的底層, 可以進行一些非常底層的操作(用OC是無法現(xiàn)實的, 不好實現(xiàn))
1、在程序運行過程中, 動態(tài)創(chuàng)建一個類(比如KVO的底層實現(xiàn))
2灶体、在程序運行過程中, 動態(tài)地為某個類添加屬性\方法, 修改屬性值\方法
3阅签、遍歷一個類的所有成員變量(屬性)\所有方法
我們可以通過#import 、#import 查看相關函數(shù):
1蝎抽、objc_msgSend: 給對象發(fā)送消息
2政钟、class_copyMethodList: 遍歷某個類所有的方法
3、class_copyIvarList: 遍歷某個類所有的成員變量
4、class_.....這是我們學習runtime必須知道的函數(shù)
我們寫的代碼在程序運行過程中都會被轉化成runtime的C代碼執(zhí)行养交,OC中一切都被設計成了對象精算,我們都知道一個類被初始化成一個實例,這個實例是一個對象碎连。實際上一個類本質上也是一個對象灰羽,在runtime中用結構體表示。
定義如下:
class在runtime的定義:
實現(xiàn)原理
參考文檔:(https://github.com/bang590/JSPatch/wiki/JSPatch-實現(xiàn)原理詳解)
接入流程
1鱼辙、 在General 的 LinkFrameworks and Libraries里面添加javascriptcore.framework廉嚼,這個庫里主要用于JS與OC語言的橋接,比如一些數(shù)據類型間的相互轉化倒戏。
2怠噪、Podfile添加? pod 'JSPatch ' 并pod install
3、在代碼中添加使用JS和下載JS的代碼
本地聯(lián)調:
網絡聯(lián)調:
更新頻次的問題
我之前看到很多人把使用JS和下載JS的代碼都放在了 didFinishLaunchingWithOptions:這個方法峭梳。我覺得有所不妥舰绘,因為如果這個app用戶一直放在手機的后臺(比如微信),并且也沒出現(xiàn)內存警告的話葱椭,這個方法應該一直不會調用捂寿。我建議的是:使用JS文件的代碼放在didFinishLaunchingWithOptions:而下載JS文件的代碼放在applicationDidBecomeActive: 因為這個方法在程序啟動和后臺回到前臺時都會調用。并且我建議設置一個間隔時間孵运,根據一些_數(shù)據和權衡之后我們采用的是間隔時間設為1小時秦陋。 也就是說每次來到這個方法時,先要檢測是距離上次發(fā)請求的時間間隔是否超過1小時治笨,超過則發(fā)請求驳概,否則跳過。
安全問題
由于JS代碼是動態(tài)下發(fā)的旷赖,所以必須要考慮傳輸過程中的安全問題顺又。在這種應用場景,我們不怕被截獲等孵,主要擔心被中間篡改稚照。我們在傳輸過程中對JS文件進行了RSA簽名加密,流程如下:
詳細描述俯萌,我們可以查看鏈接https://segmentfault.com/a/1190000003689114
1果录、服務端計算出腳本文件的 MD5 值,作為這個文件的數(shù)字簽名咐熙。
2弱恒、服務端通過私鑰加密第 1 步算出的 MD5 值,得到一個加密后的 MD5 值棋恼。
3返弹、把腳本文件和加密后的 MD5 值一起下發(fā)給客戶端锈玉。
4、客戶端拿到加密后的 MD5 值琉苇,通過保存在客戶端的公鑰解密嘲玫。
5、客戶端計算腳本文件的 MD5 值并扇。
6去团、對比第 4/5 步的兩個 MD5 值(分別是客戶端和服務端計算出來的 MD5 值),若相等則通過校驗穷蛹。
JSPatch語法
JSPatch語法參見鏈接:(https://github.com/bang590/JSPatch/wiki/JSPatch-基礎用法)
畢竟JSPatch語法 并不是一個正式的語種土陪,也沒有像樣的IDE開發(fā)繼承環(huán)境,大家不會投入太大的精力來仔細學習肴熏,好在我們有語法轉換工具鬼雀,JSPatchConvertor,這個工具可以把OC語言轉換成JS語言蛙吏。當然比較復雜的語法不能用這工具直接搞定源哩,還需要人工修改。修改的內容有CGRect結構體鸦做、常量UIControlStateNormal励烦、block等等。
更多思考
1泼诱、 接入了JSPatch之后坛掠,IOS的線上BUG 看上去就不向以前那樣“猛如虎”了,但是這僅僅是一個緊急預案措施治筒,以前規(guī)范的流程還是需要遵守屉栓。
2、 每一次本版本用JSPatch解決的線上Bug耸袜,下個版本必須用OC代碼寫入項目中友多,不能允許補丁代碼的存留超過一個版本。
3堤框、 可以動態(tài)開發(fā)功能模塊域滥,但是成本太大,可以適當修改胰锌,作為模塊入口,但是整個開發(fā)模塊功能藐窄,難度太大资昧。
4、 每次用JSPatch解決掉的線上BUG 應當有一個專門的文檔記錄荆忍,遇到重復錯誤必須寫casestudy格带。
問題
當我們?yōu)樾迯蚥ug撤缴,下發(fā)JS腳本到APP本地,會遇到bug沒有修復的問題叽唱∏唬可能有兩種情況:
1、 JS腳本語法錯誤導致無法執(zhí)行棺亭。解決辦法:檢查腳本虎眨,修復調試。
2镶摘、 JS腳本由于網絡加載并行嗽桩,導致執(zhí)行延時,出現(xiàn)bug的class沒有被及時替換凄敢。解決辦法:重啟設備