淺析JSPatch的使用
一. 背景介紹
背景:iOS作為蘋果獨(dú)家開發(fā)和運(yùn)營的生態(tài)圈村生,具有非常封閉的運(yùn)作環(huán)境冰木,其App上線需要通過提交沿量,排隊浪慌,審核,上線這四個很長的過程朴则,過往的排隊時間長至于7~15天权纤,對一個軟件迭代就有了極大的限制,如果產(chǎn)品中出現(xiàn)了小bug, 需要修復(fù)問題的話就是及其困難的了乌妒,雖然目前蘋果的審核時間已經(jīng)短至1天汹想,但是其App更新方式為全量更新,如若為了修復(fù)一個極小的問題而強(qiáng)迫用戶更新一個版本的話撤蚊,體驗也是不好的古掏, 所以在多年的技術(shù)進(jìn)化中,產(chǎn)生了App熱更新這樣的一個需求侦啸。
發(fā)展: 在這幾年來的iOS開發(fā)界槽唾,各個廠商和各種極客各現(xiàn)神通,通過各種黑科技來實現(xiàn)各種熱更新技術(shù)的實現(xiàn)光涂,在iOS7之前庞萍,著名的有Wax框架,Wax框架主要使用Lua語言進(jìn)行腳本實現(xiàn)忘闻,并且需要在原生的App中植入Wax的腳本引擎钝计,使用上也不是太方便,所以逐漸被淘汰。2013年私恬,蘋果在iOS7中引入了原生框架JavascriptCore這樣一個原生框架债沮,徹底打開了JS腳本和native調(diào)用之間的橋梁,也為熱更新技術(shù)的實現(xiàn)提供了原生的技術(shù)支持本鸣,各種極客和軟件開發(fā)商都定制自己的腳本來調(diào)用本地的部分代碼疫衩,但是都沒有統(tǒng)一的方案,在這一個沉淀的過程中永高,產(chǎn)生了較為成熟易用的JSPatch框架隧土。
二. JSPatch介紹
- 誕生:JSPatch誕生于2015年5月提针,最初出自于騰訊廣研院的高級iOS開發(fā)工程師@bang的個人項目命爬,其超級深厚的開發(fā)基礎(chǔ)和過人的天賦在這個項目中表現(xiàn)的淋漓盡致,其基本原理為使用javascriptcore中提供的調(diào)用Objective-C的原生入口辐脖,并充分利用Objective-C的動態(tài)特點(diǎn)來在運(yùn)行時修改方法的入口和實現(xiàn)饲宛,用于替換原有的舊方法。 目前JSPatch在Github上已經(jīng)開源嗜价,擁有大量的擁簇艇抠,并且充分在騰訊,阿里久锥,百度各個大廠的產(chǎn)品中得到了驗證家淤,騰訊甚至提供了JSPatch托管SDK平臺用于商用,可見其實現(xiàn)已經(jīng)成熟到了商用的地步瑟由,可以放心使用了絮重。以下為其他開發(fā)者總結(jié)的JSPatch和Wax的區(qū)別:
基本原理
- Objective-C是動態(tài)語言,具備運(yùn)行時特性歹苦,所以能夠在運(yùn)行時通過類名稱或者方法的名稱來獲取執(zhí)行入口青伤,并且進(jìn)行實際調(diào)用,而且還可以通過runtime特性來swizzle各種方法殴瘦,進(jìn)行動態(tài)修改狠角。
- 通過類和方法名稱進(jìn)行運(yùn)行時調(diào)用的片段:
Class class = NSClassFromString(@"UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString(@"viewDidLoad");
[viewController performSelector:selector];
動態(tài)替換類方法為新方法的片段:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);
正是由于OC語言具有如上片段展示的runtime時決定調(diào)用方法的特性,成就了熱更新的基礎(chǔ)原理蚪腋。
具體的實現(xiàn)原理比較復(fù)雜丰歌,作者已經(jīng)開源了代碼,并且在博客中寫明了實現(xiàn)過程中的各種問題屉凯,如果有興趣的可以參閱以下鏈接:
JSPatch在github的源地址,
JSPatch詳細(xì)實現(xiàn)原理
三. 使用方法
- 基本語法解析
下面展示一下OC代碼和熱更新的JS腳本的對應(yīng)代碼片段立帖,例如線上 APP 有一段代碼出現(xiàn) bug 導(dǎo)致 crash:
@implementation JPTableViewController
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *content = self.dataSource[[indexPath row]]; //可能會超出數(shù)組范圍導(dǎo)致crash
JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
[self.navigationController pushViewController:ctrl];
}
...
@end
可以通過下發(fā)這樣一段 JS 代碼,覆蓋掉原方法神得,修復(fù)這個 bug:
//JS
defineClass("JPTableViewController", {
//instance method definitions
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row()
if (self.dataSource().length > row) { //加上判斷越界的邏輯
var content = self.dataArr()[row];
var ctrl = JPViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(ctrl);
}
}
}, {})
除了修復(fù)一些bug之外厘惦,也可以動態(tài)的修改一些App的行為, 詳細(xì)越發(fā)可以查閱
JSPatch語法WIKI
- SDK接入使用
前面已經(jīng)提到,騰訊已經(jīng)將JSPatch進(jìn)行了商用包裝宵蕉,使用方式非常簡單酝静,進(jìn)入JSPatch的官方站點(diǎn),下載SDK羡玛,將SDK拖入工程别智,然后添加官方j(luò)avascriptcore.framework就可以按照文檔進(jìn)行使用了,騰訊官方還進(jìn)行了后端平臺托管稼稿,使用者在后端注冊App獲取Key薄榛,然后在App端初始化即可使用,需要熱更新的腳本也配置在后端進(jìn)行下發(fā)让歼,使用非常方便簡潔敞恋,該平臺提供每天1W次下發(fā)的免費(fèi)量,超過1W次需要收費(fèi)服務(wù)谋右,詳細(xì)使用方式可以查閱:
JSPatch官方網(wǎng)站以及介紹
- 源代碼引入使用
首先構(gòu)建一個簡單的demo, demo潔面只有一個按鈕硬猫,按鈕下面一個Label框,demo的簡單邏輯是改执,點(diǎn)擊按鈕之后啸蜜,Label框背景顏色變?yōu)樗{(lán)色,并且顯示native code字樣辈挂。
原始代碼片段如下:
初始運(yùn)行界面為:
點(diǎn)擊changeColor按鈕之后衬横,事件響應(yīng)并執(zhí)行,運(yùn)行界面變?yōu)椋?/p>
以上為native代碼的原始邏輯终蒂,下面我們開始進(jìn)行動態(tài)替換:
- 思路
我們要動態(tài)替換changeColor的點(diǎn)擊事件蜂林,其實就是需要動態(tài)替換clickChangeColorEvent這個函數(shù),大家可以去按照上面的JSPatch的語法wiki中去查閱詳細(xì)語法后豫,我們這里有更簡單直接的方法悉尾,JSPatch的作者為了使用者方便,開發(fā)了
JSPatchConverter這個工具挫酿,大家可以去詳細(xì)看一下使用构眯,也可以直接下載成品App, 我們這里使用成品App進(jìn)行JS代碼生成,只需要在App的左側(cè)寫入要替換的方法早龟,點(diǎn)擊convert按鈕惫霸,右邊窗口即可生成熱更新的腳本代碼,如圖:
將右邊窗口的成品JS片段自己保留出來葱弟,然后放在自建的服務(wù)器準(zhǔn)備下發(fā)使用壹店。
- 本地工程準(zhǔn)備
從github中下載JSPatch的源碼,并在源碼工程中找到JSPatch文件夾芝加,將該文件夾拖入demo工程硅卢,如圖:
同時添加蘋果官方的javascriptcore.framework,如圖:
- 代碼實現(xiàn)
首先要在AppDelegate.h中添加對應(yīng)的頭文件:
#import "JSPatch/JPEngine.h"
繼而在iOS的App啟動入口函數(shù)中添加JSEngine的初始化:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[JPEngine startEngine];
return YES;
}
以上兩步已經(jīng)完成了JSEngine的簡單的初始化過程,然后進(jìn)入下一步,獲取遠(yuǎn)端腳本的過程将塑。 將前面在JSConverter中生成的腳本代碼自己放入自建服務(wù)器中(這一步自己尋找后端兄弟協(xié)助完成), 然后在實現(xiàn)一個獲取腳本的函數(shù)脉顿,進(jìn)行腳本獲取,并將執(zhí)行獲取腳本的函數(shù)放在[JPEngine startEngine]之后執(zhí)行点寥,然后在獲取到腳本之后艾疟,執(zhí)行腳本行為,以下為完整范例:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[JPEngine startEngine];
[self getAndRunJSPatchScript];
return YES;
}
- (void)getAndRunJSPatchScript
{
NSURLSession *urlSession = [NSURLSession sharedSession];
NSMutableURLRequest *httpRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxxxx/api"]];
[httpRequest setHTTPMethod:@"POST"];
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:httpRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error)
{
NSString *targetJSPatchScript = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[JPEngine evaluateScript:targetJSPatchScript];
}
}];
[dataTask resume];
}
然后運(yùn)行新的程序敢辩,點(diǎn)擊changeColor按鈕蔽莱,行為已經(jīng)發(fā)生了改變:
可以看到,圖中的色塊已經(jīng)變成了黃色戚长,圖中的提示語言也顯示JSPatch代碼進(jìn)行了執(zhí)行盗冷,而這一個過程當(dāng)中,native的邏輯代碼并沒有改變历葛。
- 歸納
上面代碼片段中的getAndRunJSPatchScript函數(shù)只是簡單的調(diào)用了接口正塌,獲取了腳本,然后執(zhí)行恤溶。更長遠(yuǎn)的規(guī)劃,服務(wù)端完全可以實現(xiàn)一個腳本管理服務(wù)帜羊,提供一系列結(jié)構(gòu)咒程,將不同App版本,不同運(yùn)營行為的腳本進(jìn)行分類管理讼育,App只需要上報自己的App基本信息帐姻,然后由服務(wù)端來返回要熱更新的腳本,更加的靈活和體系化奶段。
總結(jié)
上述內(nèi)容可以看到JSPatch確實是一個很方便的熱更新庫饥瓷,但是個人認(rèn)為在App的開發(fā)過程中,大家還是應(yīng)該把質(zhì)量管控在開發(fā)階段痹籍,不可依賴于這樣的熱更新修復(fù)呢铆,這樣的修復(fù)可以用于應(yīng)急來修補(bǔ)漏網(wǎng)的bug, 但是不建議作為App的主要邏輯開發(fā)層,因為這樣的會帶來更多的外部管理App的功能蹲缠,從某種意義上來說也添加了運(yùn)營的復(fù)雜性棺克, 而且在蘋果主推的未來開發(fā)語言swift中,這種OC語言所具有的動態(tài)性將會不再存在线定,以這套原理來運(yùn)作的JSPatch庫也就無從使用了娜谊,但是在當(dāng)下的實際OC仍然是很多App的主開發(fā)語言的時代,這個熱更新修復(fù)的庫還是非常非常實用的斤讥,至于各位開發(fā)者將會對這個庫依賴多深纱皆,這就是自己定奪的問題了。