iOS 動態(tài)化熱修復(fù)方案
Warnning
純粹是技術(shù)分享我擂,不要用于上架操作约素!
前言
iOS熱修復(fù)方案經(jīng)過JSPatch事件后,也消停了很久。bang神在《JSPatch – 動態(tài)更新iOS APP》中曾提到硅急,為了更符合Apple的規(guī)則,即《Apple Developer Program License Agreement》 里3.3.2提到的不可動態(tài)下發(fā)可執(zhí)行代碼佳遂。
JSPatch特地繞了js的圈子营袜,從而實現(xiàn)曲線救國、實現(xiàn)熱更新的方案丑罪。但是事實證明了Apple對于這種方案也是不認可的荚板,根本的原因還是在于JSPath做得太過極致--支持絕大部分的OC/C語法凤壁。
思考
既然JSPatch繞道js的方法,已經(jīng)被Apple拒絕了跪另,那么就再次回到原點拧抖,重新出發(fā)。新的框架或者新的方案我覺得至少有一個充分條件免绿,就是不能太極致唧席。
Objective-C作為一種動態(tài)的語言,因此能夠動態(tài)執(zhí)行所有OC語法是正常的针姿,Aspects類似的框架也是被Apple認可的袱吆。至于是否需要運行所有的C函數(shù),這個有待商榷距淫。
第二個方面绞绒,則是放棄javascript/lua等類似語言作為更新的腳本,而是采用原生的Objective-C作為更新的腳本語言榕暇。
動態(tài)運行C函數(shù)
C語言是沒有反射機制的蓬衡,作為一門編譯型語言,在編譯期間就已經(jīng)生成機器碼彤枢。因此如果要從字符串中獲取到對應(yīng)的函數(shù)指針狰晚,那么大概有兩種方法:
- 建立映射表, 將函數(shù)名和函數(shù)指針建立一個映射表。
- dlsym, 根據(jù)動態(tài)鏈接庫操作句柄與符號缴啡,返回符號對應(yīng)的地址壁晒。
第二種是目前JSPatch采用的辦法,當然也被Apple警告了业栅。dlsym功能非常強悍秒咐,是獲取函數(shù)指針的最優(yōu)解。
第一種局限性非常大碘裕,但是沒有用到黑魔法携取。
采用Objective-C作為更新的腳本語言
通過flex/yacc,直接解析Objective-C語法帮孔,不再采取js/lua等腳本語言雷滋。
DynamicOC
經(jīng)過上面的思考,在最近業(yè)余中做了DynamicOC的項目,百分百原生支持采用Objective-C作為更新的腳本語言。
當然動態(tài)運行C函數(shù)還是采用dlsym獲取函數(shù)指針的辦法讯壶,后面會逐步改為映射表的做法懒构。
原理
DynamicOC使用flex/yacc進行詞法解析和語法分析,轉(zhuǎn)為一顆語法生成樹AST。
然后通過解析每個節(jié)點,從而執(zhí)行相應(yīng)的代碼。因為采用的是Objective-C作為腳本語言烹俗,因此極容易適配爆侣。
功能特點
- 動態(tài)執(zhí)行OC代碼
- 動態(tài)執(zhí)行C函數(shù)和block異步調(diào)用
- 動態(tài)添加屬性
- 動態(tài)替換方法
- 動態(tài)添加方法
- 有完善的單元測試
- flex/yacc實現(xiàn)強大的OC語法解析器
- 支持CGRect/CGSize/CGPoint/NSRange/UIEdgeInsets/CGAffineTransform常用結(jié)構(gòu)體
...
基本用法
動態(tài)執(zhí)行block
NSString* text = @" \
__block int result = 0;\
UIView* view = [[UIView alloc]init];\
void(^blk)(int value) = ^(int value){\
view.tag = value;\
};\
blk(1024);\
return view.tag;";
ASTNode* root = [ASTUtil parseString:text];
ASTVariable* result = [root execute];
NSAssert([result.value doubleValue] == 1024, nil);
動態(tài)執(zhí)行C函數(shù)
int echo(int value) {
return value;
}
NSString* text = @" \
[OCCfuntionHelper defineCFunction:@\"echo\" types:@\"int, int\"]; \
return echo(1024);";
ASTNode* root = [ASTUtil parseString:text];
ASTVariable* result = [root execute];
NSAssert([result.value doubleValue] == 1024, nil);
動態(tài)添加Property
NSString* text = @" \
[OCCfuntionHelper defineCFunction:@\"objc_setAssociatedObject\" types:@\"void,id,void *,id,unsigned int\"];\
[OCCfuntionHelper defineCFunction:@\"objc_getAssociatedObject\" types:@\"id,id,void *\"];\
NSString* key = @\"key\"; \
objc_setAssociatedObject(self, key, @(1024), 1);\
return objc_getAssociatedObject(self, key);";
ASTNode* root = [ASTUtil parseString:text];
ASTVariable* result = [root execute];
NSAssert([result.value doubleValue] == 1024, nil);
已支持語法
- [x] if/else while do/while for
- [x] return break continue
- [x] i++ i-- ++i --i
- [x] +i -i !i
- [x] + - * / %等四則運算
- [x] >> << & | ^ 等位運算
- [x] && || >= <= != > < 等比較運算
- [x] ?:
- [x] __block
- [x] array[i] dict[@""]
- [x] @666 @() @[] @{}
- [x] self super
- [x] self.property
- [x] self->_property
- [x] most of objective-c keyword
TODO
- [ ] @available()
- [ ] [NSString stringWithFormat:"%d",value] : use [NSString stringWithFormat:"%@",@(value)] instead。
- [ ] dispatch_async / dispatch_after ...
- [ ] *stop =YES, in block
- [ ] fix bugs
參考鏈接
JSPatch – 動態(tài)更新iOS APP
iOS 動態(tài)化的故事
Apple Developer Program License Agreement
滴滴 iOS 動態(tài)化方案 DynamicCocoa 的誕生與起航