前言:
伴隨著企業(yè)的快速發(fā)展弯囊,承載著移動(dòng)互聯(lián)網(wǎng)業(yè)務(wù)的App的更新迭代要求越來越高。尤其是在中國誊爹,App的迭代速度很快芬首,有時(shí)App需要緊急發(fā)版來處理線上業(yè)務(wù)和技術(shù)等問題。由于Apple審核制度的限制吮成,在Apple提速了審核速度的情況下橱乱,大多數(shù)App審核往往需要1到2天。但是這仍舊無法滿足App快速更新的需求粱甫。所以泳叠,想不通過發(fā)版,解決線上問題的方案一直為iOS端技術(shù)研發(fā)人員所青睞茶宵。
但是隨著公開版本的JSPatch被Apple拒絕危纫。熱更新這個(gè)概念似乎冷了下來。但是App更新與審核速度的矛盾從沒有解決。分析种蝶、構(gòu)思契耿,探索和找尋解決這個(gè)問題的合理方案。
理論可行性分析:
查閱了幾種熱更新的方案螃征,JSPatch搪桂、Aspects、Stringer盯滚、TTPatch踢械。分別對其實(shí)現(xiàn)原理、上架審核現(xiàn)狀以及文檔和資料完備性等方面進(jìn)行分析魄藕。
拒絕JSPatch:
關(guān)于Apple拒絕JSPath事件的分析内列。
Apple對于App的開發(fā)傾向于Native。但是拒絕JSPatch并不是因?yàn)槠涫褂昧薐S動(dòng)態(tài)下發(fā)代碼泼疑,而是因?yàn)檫@種方式存在著安全漏洞德绿。可能被第三方篡改造成隱患退渗。正式因?yàn)檫@樣移稳,所以在市面上滴滴出行App仍舊存在熱更新。從一些資料上看到会油,滴滴的方案是從技術(shù)層面上對于下發(fā)的JS代碼做了安全性校驗(yàn)个粱。使得滴滴的App能夠識別這些JS是由滴滴下發(fā)的。從而避免了Apple拒絕JSPatch熱更新的安全漏洞翻翩。所以都许,重要的是安全性。而不是熱更新的手段嫂冻。
關(guān)于Aspects方案:
Aspects的原理與消息轉(zhuǎn)發(fā)機(jī)制密切相關(guān)胶征。Aspects主要是利用了forwardInvocation進(jìn)行轉(zhuǎn)發(fā),Aspects其實(shí)利用和kvo類似的原理桨仿,通過動(dòng)態(tài)創(chuàng)建子類的方式睛低,把對應(yīng)的對象isa指針指向創(chuàng)建的子類,然后把子類的forwardInvocation的IMP替成__ASPECTS_ARE_BEING_CALLED__服傍,假設(shè)要hook的方法名XX,在子類中添加一個(gè)Aspects_XX的方法钱雷,然后將Aspects_XX的IMP指向原來的XX方法的IMP,這樣方便后面調(diào)用原始的方法吹零,再把要hook的方法XX的IMP指向_objc_msgForward罩抗,這樣就進(jìn)入了消息轉(zhuǎn)發(fā)流程,而forwardInvocation的IMP被替換成了__ASPECTS_ARE_BEING_CALLED__灿椅,這樣就會進(jìn)入__ASPECTS_ARE_BEING_CALLED__進(jìn)行攔截處理套蒂,這樣整個(gè)流程大概結(jié)束钞支。
關(guān)于Stringer方案:
雖然Stringer聲稱速度快又好,但是怎奈查看github庫操刀,當(dāng)前開發(fā)版本號是0.3.0伸辟,可以認(rèn)為是還不夠成熟。并且其公開文檔馍刮、資料極少。Demo也不完備窃蹋。這給實(shí)際應(yīng)用帶來了麻煩卡啰。
綜合分析:
于是有了滴滴的 DynamicCocoa 這種方案,繞了一個(gè)更大的道警没,從編譯階段入手匈辱,通過 clang 把 OC 代碼編譯成自己定制的 JS 格式,再動(dòng)態(tài)下發(fā)去執(zhí)行杀迹,做到原生開發(fā)亡脸,動(dòng)態(tài)運(yùn)行,主打動(dòng)態(tài)添加功能树酪,當(dāng)然順便把修 bug 也給支持了浅碾。手機(jī) QQ 內(nèi)部也有一個(gè)類似的方案,不過更進(jìn)一步续语,他們通過 clang 把 OC 代碼編譯成自己定制的字節(jié)碼動(dòng)態(tài)下發(fā)垂谢,然后開發(fā)一個(gè)虛擬機(jī)去執(zhí)行(驚呆了),同樣實(shí)現(xiàn)了原生開發(fā)疮茄,動(dòng)態(tài)運(yùn)行滥朱,都是 NB 得很的方案。只要底層處理做得足夠好力试,也是個(gè)成本低收益高的方案徙邻,不過目前都還沒開源,還沒能看到實(shí)際效果和可靠的源碼畸裳。
綜合查閱以上幾個(gè)熱更新方案缰犁,遴選出Aspects方案。原因是躯畴,此方案可以實(shí)現(xiàn)需要的代碼修復(fù)功能民鼓,而且Aspects庫與iOS庫相關(guān)。這可以作為通過審核的有力依據(jù)蓬抄。
This is stable and used in hundreds of apps since it’s part of PSPDFKit,
an iOS PDF framework that ships with apps like Dropbox or Evernote.
綜上如果你是企業(yè)賬號那么方案就是JSPatch 1.7.2版本加自己管理補(bǔ)丁,因?yàn)樗@個(gè)平臺超過1萬會收費(fèi).但是人家js轉(zhuǎn)OC的代碼轉(zhuǎn)換器都有了開發(fā)成本很低的丰嘉。
如果是需要上架的App,并且團(tuán)隊(duì)的后臺安全性沒有得到充分保證的情況下嚷缭,Aspects是穩(wěn)妥的方案饮亏。
Aspects深入分析:
理論分析--面向切面(AOP)編程:
通過預(yù)編譯和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)耍贾。可以實(shí)現(xiàn)不修改原始類的實(shí)現(xiàn)無入侵式改變應(yīng)用行為路幸,相對來講荐开,實(shí)現(xiàn)簡單,易于維護(hù)简肴』翁可以在需要的某一個(gè)類或?qū)嵗刑砑右恍┪覀冏约旱膶?shí)現(xiàn),只針對某個(gè)切面進(jìn)行Hook操作砰识,這個(gè)就是面向切面的概念(AOP能扒,aspect-oriented programming),框架Aspects是 AOP 編程的框架辫狼。
AOP是采用使用運(yùn)行時(shí)hook方法實(shí)現(xiàn)的原理(iOS的消息轉(zhuǎn)發(fā)初斑、swzziling)。
Aspects可以做什么:
AOP在開發(fā)中是一個(gè)非常重要的思想膨处,我們希望將需求分離到非業(yè)務(wù)邏輯的方法中见秤,盡可能的不影響業(yè)務(wù)邏輯的代碼。主要的應(yīng)用場景大概有以下幾種:
參數(shù)校驗(yàn):網(wǎng)絡(luò)請求前的參數(shù)校驗(yàn)真椿,返回?cái)?shù)據(jù)的格式校驗(yàn)等等鹃答;
無痕埋點(diǎn):統(tǒng)一處理埋點(diǎn),降低代碼耦合度突硝;
頁面統(tǒng)計(jì):幫助統(tǒng)計(jì)頁面訪問量挣跋;
事務(wù)處理:攔截指定事件,添加觸發(fā)事件狞换;
異常處理:發(fā)生異常時(shí)使用面向切面的方式進(jìn)行處理避咆;
熱修復(fù):AOP可以讓我們在某方法執(zhí)行前后或者直接替換為另一段代碼,我們可以根據(jù)這個(gè)思路修噪,實(shí)現(xiàn)bug修復(fù).
Aspects使用注意事項(xiàng):
會有額外的系統(tǒng)開銷查库,要避免高頻調(diào)用。hook方法不能修改 @"retain", @"release", @"autorelease", @"forwardInvocation:"黄琼。hook類中的dealloc方法只能AspectPositionBefore樊销。Aspects會產(chǎn)生額外開銷,性能不高脏款,不建議高頻調(diào)用围苫。
關(guān)于封裝庫的使用經(jīng)驗(yàn):
JS替換實(shí)例方法會覆蓋原類和其分類的方法實(shí)現(xiàn)。注意有返回值和無返回值JS方法的靈活運(yùn)用撤师。注意無參數(shù)剂府、有參數(shù)、多參數(shù)JS方法的調(diào)用剃盾。JS方法同樣可以修改某一類中代理方法的實(shí)現(xiàn)腺占。與一般方法相同淤袜。JS修改方法,可以獲得上下文衰伯,參數(shù)铡羡,方法名∫饩ǎ可以根據(jù)具體需要來進(jìn)行邏輯處理烦周。
部分JS代碼:
fixInstanceMethodAfter('UBTestViewController', 'initViewLayout', function(instance, originInvocation, originArguments){
? ? //runVoidInstanceWithNoParamter(instance, 'showProgressForCommit');
? ? //runVoidInstanceWith1Paramter(instance, 'showText:','這是熱更新測試');//替換某個(gè)方法,為該類的已有方法
? ? //下面是用JS語法設(shè)置OC的實(shí)例對象
? ? var coffeeVo = runClassWithNoParamter('UBCoffeeModel', 'new');
? ? runVoidInstanceWith1Paramter(coffeeVo, 'setBrand:','luckin');
? ? runVoidInstanceWith1Paramter(coffeeVo, 'setPrice:','16');
? ? runVoidInstanceWith1Paramter(instance, 'setCoffee:',coffeeVo);
? ? //runVoidInstanceWith2Paramter(instance, 'resetMyCoffee::',coffeeVo);
? ? console.log('這是熱更新測試 替換方法驗(yàn)證##');
});
對于block可以參考如下代碼怎顾。
//參數(shù)作為方法的參數(shù)论矾,在JS中的調(diào)用(makeMyCoffeeComplete:的參數(shù)在原OC實(shí)現(xiàn)是一個(gè)block)
fixInstanceMethodReplace('UBTestViewController', 'makeMyCoffeeComplete:', function(instance, originInvocation, originArguments){
? ? if (originArguments[0]) {//取出參數(shù),這里在對應(yīng)的OC中杆勇,這個(gè)參數(shù)是block也就是function
? ? ? ? var callback = originArguments[0];
? ? ? ? callback(20);//block? 作為參數(shù),直接調(diào)用
? ? }
? ? console.log('這是熱更新測試 替換block方法驗(yàn)證##' + originArguments[0]);
});
部分對Aspects的封裝代碼(借鑒了網(wǎng)上搜到的信息):
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector obj1:(id)obj1 obj2:(id)obj2 {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [instance performSelector:NSSelectorFromString(selector) withObject:obj1 withObject:obj2];
#pragma clang diagnostic pop
}
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector withObjects:(NSArray *)objects {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [[UBFreeFixManger sharedInstance] performSelector:NSSelectorFromString(selector) target:instance? withObjects:objects];
#pragma clang diagnostic pop
}
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [instance performSelector:NSSelectorFromString(selector)];
#pragma clang diagnostic pop
}
特別的饱亿,基于Aspects的熱修復(fù)蚜退,其目的是“熱修復(fù)”,也僅僅是熱修復(fù)彪笼。不建議通過這種方式隨意修改一般功能邏輯钻注。我想這也是基于Aspects的熱修復(fù)能得到Apple認(rèn)可的原因。
以上可行性分析很多資料借鑒和來源于網(wǎng)絡(luò)配猫,Aspects方案經(jīng)過筆者仔細(xì)實(shí)踐幅恋。Demo源碼可以留言索取。
參考資料:
1泵肄、TTPatch【類JSPatch】<有成功案例捆交、JS代碼下發(fā),轉(zhuǎn)成原生代碼>
《TTPatch使用》
http://www.reibang.com/p/470a9e49b4f2
2腐巢、iOS中OC轉(zhuǎn)Javascript的工具
https://blog.csdn.net/u013602835/article/details/52777461
3品追、Aspects<數(shù)百成功案例、JS代碼下發(fā)冯丙,轉(zhuǎn)成原生代碼>
http://www.reibang.com/p/2c93446d86bd
4肉瓦、《iOS AOP框架Aspects實(shí)現(xiàn)原理》
http://www.reibang.com/p/0d43db446c5b
5、《輕量級低風(fēng)險(xiǎn) iOS 熱更新方案》
https://mp.weixin.qq.com/s/2re_s3NmOvE9RXlbGQqGDA
6胃惜、《iOSHotFixDemo》
https://github.com/3KK3/iOSHotFixDemo/tree/master/iOSHotFixDemo
7泞莉、《OS 2020 熱更新》
https://blog.csdn.net/u013712343/article/details/107932706
8、《《【iOS 教程】亮劍: Stinger到底能比Aspects快多少》
https://blog.csdn.net/weixin_47143210/article/details/105603880》
9船殉、iOS使用Aspects做簡單熱修復(fù)原理
https://www.pianshen.com/article/9576238931/
10鲫趁、動(dòng)態(tài)交換方法實(shí)現(xiàn)
https://blog.csdn.net/WangErice/article/details/51211328
11、iOS AOP框架Aspects實(shí)現(xiàn)原理
http://www.reibang.com/p/0d43db446c5b
12利虫、Mac啟動(dòng)本地服務(wù)
http://www.reibang.com/p/90d5fa728861
13饮寞、熱更新方案
https://srxboys.github.io/2018/06/03/%E7%83%AD%E6%9B%B4%E6%96%B0%E6%96%B9%E6%A1%88/