背景介紹
IOS平臺(tái)提交審核的周期太長(zhǎng),快則45天狞甚,慢則半個(gè)月或者20天锁摔,如果碰到圣誕節(jié)等假日,可能一個(gè)月都有可能哼审,如果碰到被拒谐腰,那就更坑爹了,那時(shí)基本靠人品∩埽現(xiàn)在蘋果的審核周期變短了十气,有可能是12天審核就通過(guò)了,具體是不是以后都是這么快春霍,就不清楚了砸西。
JSPatch利用javascript調(diào)用任何Objective-C原生接口,替換任意的Objective-C原生接口址儒,修復(fù)線上bug芹枷。主要通過(guò)下發(fā)JS腳本替換原生Objective-C代碼。
JSPatch:GitHub地址
SDK接入
準(zhǔn)備
該demo的GitHub地址
-
在JSPatch平臺(tái)上注冊(cè)賬號(hào)莲趣,添加新App鸳慈,會(huì)生成唯一AppKey
- 在該平臺(tái)下載SDK解壓后將JSPatch.framework拖入項(xiàng)目
- 添加依賴框架libz.dylib和JavaScriptCore.framework
添加代碼
在AppDelegate.m的didFinishLaunchingWithOptions中添加如下代碼:
//添加頭文件
#import <JSPatch/JSPatch.h>
//1
[JSPatch startWithAppKey:JSPatchKey] //JSPatchKey是創(chuàng)建App獲得的AppKey
//2
[JSPatch sync]
//1中startWithAppKey傳入平臺(tái)申請(qǐng)的appKey,啟動(dòng)JSPatch SDK,同時(shí)會(huì)自動(dòng)執(zhí)行已下載到本地的patch.
//2中sync與JSPatch平臺(tái)后臺(tái)更新妖爷,詢問(wèn)是否有patch更新,如果有更新會(huì)自動(dòng)下載并執(zhí)行理朋。startWithAppKey并不會(huì)詢問(wèn)后臺(tái)patch更新絮识,必須調(diào)用sync方法。
注意:實(shí)時(shí)性不高的App只需在didFinishLaunchingWithOptions處調(diào)用一次嗽上,用戶啟動(dòng)時(shí)就會(huì)同步patch信息次舌;實(shí)時(shí)性要求高的App,在applicationDidBecomeActive處調(diào)用兽愤,用戶每次喚醒App時(shí)就同步一次后臺(tái)
測(cè)試本地腳本
在上線之前需要對(duì)腳本進(jìn)行本地測(cè)試查看運(yùn)行是否正常彼念,注意JSPatch平臺(tái)規(guī)范挪圾,JS腳本文件名必須是main.js。SDK提供了方法+testScriptInBundle用于發(fā)布前測(cè)試逐沙,調(diào)用該方法后哲思,JSPatch會(huì)在當(dāng)前項(xiàng)目的bundle尋找main.js。
注意:+testScriptInBundle不能與+startWithAppKey一起調(diào)用吩案,+testScriptInBundle只能用于本地測(cè)試棚赔,測(cè)試完需刪除
Demo本地實(shí)例
新建一個(gè)工程,在Main.storyboard中添加UITableView,并添加相應(yīng)約束徘郭,具體如圖靠益,設(shè)置距離上下左右的距離都是0:
設(shè)置好tableView的delegate和dataSource之后,在ViewController中定義一個(gè)數(shù)組残揉,并初始化:
self.dataArray = [NSMutableArray arrayWithObjects:@"one",@"two",@"three",@"four",@"five", nil];
之后設(shè)置tableView的相關(guān)委托代理的方法胧后,如下:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 5;
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString* cellIdentifier=@"cell";
UITableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell==nil) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.textLabel.text=[self.dataArray objectAtIndex:indexPath.row];
return cell;
}
運(yùn)行結(jié)果如下:
OK,Done抱环!一切看起來(lái)非常好壳快。
動(dòng)手修改Demo
在numberOfRowsInSection方法中,替換 return 5
為 return 6
,如下:
// return 5;
return 6; //超出dataArray的數(shù)組范圍
這樣在cellForRowAtIndexPath方法中獲取數(shù)組中的數(shù)據(jù)就會(huì)超出范圍江醇,因?yàn)閿?shù)組中只有5條數(shù)據(jù)濒憋,而我們返回了6行,少了一行陶夜。如果這種錯(cuò)誤出現(xiàn)在線上版本導(dǎo)致App崩潰凛驮,那上傳新版本的話,耗時(shí)比較長(zhǎng)条辟,所以我們現(xiàn)在用JSPatch來(lái)解決黔夭。
按照我們上面講的到JSPatch官網(wǎng)下載SDK,然后把JSPatch.framework拖入項(xiàng)目中
-
添加JavaScriptCore.framework和libz.tbd依賴框架(tbd是xcode7新增的一種文件)
-
AppDelegate中添加JSPatch/JSPatch.h頭文件羽嫡,在didFinishLaunchingWithOptions中輸入:
[JSPatch startWithAppKey:JSPatchKey]; [JSPatch sync];
由于我們這個(gè)不是線上產(chǎn)品本姥,只是在本地測(cè)試,所以我們注釋掉該代碼杭棵,替換為
JSPatch testScriptInBundle
,注意該行代碼測(cè)試完本地之后需刪除婚惫。-
通過(guò)vi編寫main.js,也可以用其他的編輯器
defineClass("ViewController", { tableView_cellForRowAtIndexPath: function(tableView, indexPath) { var cell = tableView.dequeueReusableCellWithIdentifier("cell") if (!cell) { cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell") } cell.textLabel().setText("1") return cell },
}
)
```
我只是把ViewController中的cellForRowAtIndexPath中每行顯示的數(shù)據(jù)顯示為1魂爪,這樣就可以避免崩潰了先舷,當(dāng)然根據(jù)自己的需要自行修改,具體可以參考JSPatch基礎(chǔ)用法滓侍。結(jié)果如下:
線上版本
-
進(jìn)入JSPatch平臺(tái)后臺(tái)蒋川,在我的App中添加App版本
該版本號(hào)可以通過(guò)項(xiàng)目TARGETS->General->version找到。版本號(hào)必須一致撩笆,JSPatch平臺(tái)只對(duì)該版本號(hào)下發(fā)js腳本捺球。
- 點(diǎn)擊剛添加的版本缸浦,上傳main.js即可。上傳可以直接全量下發(fā)氮兵,也可選擇開發(fā)預(yù)覽或灰度或條件下發(fā)裂逐,也可以使用自定義RSA key對(duì)腳本進(jìn)行加密簽名。上傳完成后胆剧,對(duì)應(yīng)版本App會(huì)請(qǐng)求下載腳本保持到本地絮姆,這樣線上bug就修復(fù)了
- 若后續(xù)需要對(duì)這個(gè)腳本進(jìn)行修改,可以重新上傳秩霍,App客戶端會(huì)在請(qǐng)求時(shí)發(fā)現(xiàn)腳本已更新篙悯,下載最新腳本覆蓋原來(lái)的,下次啟動(dòng)時(shí)執(zhí)行铃绒。想直接取消某個(gè)App版本的JS腳本鸽照,直接在App版本界面刪除此App版本,App客戶端請(qǐng)求時(shí)發(fā)現(xiàn)腳本已被刪除颠悬,即可刪除本地js腳本矮燎,下次啟動(dòng)不再加載。
開發(fā)預(yù)覽
從SDK1.4支持發(fā)布腳本時(shí)先針對(duì)開發(fā)版本下發(fā)赔癌。
首先開啟開發(fā)模式诞外,建議只在debug模式下開啟。
[JSPatch startAppWithKey:JSPatchKey]
#ifdef DEBUG
[JSPatch setupDevelopment]
#endif
[JSPatch sync]
接著在圖中下發(fā):
灰度和條件下發(fā)
-
灰度:SDK1.2以上灾票,按比例下發(fā)峡谊,可以修改灰度值,直至全量下發(fā)
-
條件下發(fā):只有滿足條件設(shè)備才會(huì)執(zhí)行刊苍,可以通過(guò)條件語(yǔ)句規(guī)則篩選既们。需要事先通過(guò)+setupUserData,支持多個(gè)字段,用NSDictionary表示正什,比如:
[JSPatch setupUserData:@{@"userID":userID,@"isMale":isMale}]
啥纸,需要在setupWithAppKey之前調(diào)用。
條件更新:后面的條件更新不會(huì)影響之前的條件婴氮,之前的條件依然滿足斯棒。想撤銷條件全量發(fā)布,提交空條件即可主经。SDK內(nèi)置2個(gè)信息可供條件判斷:iOS和isPad荣暮,iOS版本號(hào)只會(huì)精確到2位。
自定義RSA密鑰和安全問(wèn)題
為避免js腳本傳輸過(guò)程被中間人篡改旨怠,我們需要對(duì)js文件進(jìn)行RSA簽名加密渠驼,具體流程:
- 服務(wù)端
- 計(jì)算js文件MD5值
- 用RSA私鑰對(duì)MD值加密蜈块,與JS文件一起下發(fā)給客戶端
- 客服端
- 拿到加密數(shù)據(jù)鉴腻,用RSA公鑰解密出MD5值
- 本地計(jì)算返回的js文件MD5值
- 對(duì)比2個(gè)MD5值迷扇,相等則校驗(yàn)通過(guò),保存JS文件到本地
當(dāng)保存到本地時(shí)爽哎,越獄機(jī)器會(huì)有點(diǎn)風(fēng)險(xiǎn)蜓席,我們可以通過(guò)對(duì)稱加密保存,然后讀取時(shí)解密课锌。
客戶端和JSPatch后臺(tái)默認(rèn)有一對(duì)RSA密鑰厨内,默認(rèn)會(huì)對(duì)這對(duì)密鑰進(jìn)行加解密驗(yàn)證。也可以自定義RSA密鑰渺贤。
自定義RSA密鑰
-
生成RSA密鑰雏胃。在終端輸入下列代碼后再當(dāng)前目錄就有rsa_private_key.pem和rsa_public_key.pem。密鑰長(zhǎng)度可選1024/2048/3072/4096...
openssl > genrsa -out rsa_private_key.pem 1024 pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
SDK設(shè)置RSA Public Key志鞍〔t亮?蛻舳私尤隨DK后調(diào)用
+setupRSAPublicKey:
設(shè)置定義的RSA Public Key,必須在+sync
之前調(diào)用固棚。Public Key以字符串方式傳入统翩,換行處需要手動(dòng)加換行符\n。使用Private Key下發(fā)腳本此洲。下發(fā)腳本時(shí)勾選使用自定義RSA Key 選項(xiàng)厂汗,選擇本地的rsa_private_key.pem與腳本一起上傳。JSPatch平臺(tái)使用上傳的Private Key對(duì)腳本MD5值進(jìn)行加密呜师,然后下發(fā)客戶端娶桦。客戶端通過(guò)第二部設(shè)置的Public Key對(duì)腳本進(jìn)行驗(yàn)證匣掸,通過(guò)則運(yùn)行趟紊。注意:上傳的rsa_private_key.pem只是一次性使用,用戶必須保存rsa_private_key.pem文件
相關(guān)
JSPatch官網(wǎng):http://www.jspatch.com/
總結(jié)
JSPatch的介紹就到此為止碰酝,歡迎指正霎匈,謝謝!K桶帧铛嘱!