iOS的動態部署能極大的節約成本。蘋果的審覈週期很長,有的時候,你可能不得不等待將近2個星期去上架你的新功能或者bug。因此動態部署是有價值的。
我這裏討論的狀況不把純web應用考慮在內,由於用戶體驗過於差,偶爾出現幾個頁面還能夠,可是整個app都是的話,無疑是很是糟糕的。html
理論上講,由於OC是一門動態語言,你能夠利用runtime框架,把任意腳本語言,只要該腳本的解釋器是由c語言解釋器寫成,就能夠實現由文本向代碼的轉變。甚至你也能夠本身實現一個解釋器(也許會有人喜歡造這樣的輪子),不過過小衆的話,可能除了你本身,就沒有人能夠維護了。git
說說比較大衆的解決方案:
第一個是lua
lua目前來說,更多的是應用在遊戲上,一般遊戲的包都很大,讓玩家經常來更新大家的客戶端,那麼等着玩家刪掉大家的客戶端吧。lua第一次名聲大噪大概是被魔獸世界所應用,如今手游上cocos-2d中的lua版本即使算不上很主流,但使用的廠商仍然很多。
iOS中,由阿里維護的wax是個比較好的選擇,使用起來也比較穩定,具體的仍能夠參見github上的文檔,感受阿里的同窗的維護。github
https://github.com/alibaba/wax
lua的優勢在於:解釋的速度要比js(下面介紹)要快一些
lua的缺點在於:須要將整個lua的解釋器打入程序中,不過這也是能夠接受的。另外lua的開發者可能會少一些,在招人上可能難一些。web
第二個是js
js目前也是更多的應用在遊戲上,因爲遊戲的特性驅動所產生技術的產生與成熟,確實讓遊戲在動態部署成熟了些。cocos-2d的js版本應用要比lua版本普遍一些。騰訊的bang同窗爲咱們開源了 JSPatch,向他致敬。app
https://github.com/bang590/JSPatch
js的優勢在於:系統內置了js的解釋器(iOS7及以後),會js的人多。
js的缺點在於:解釋稍慢。框架
以上兩種動態部署方案,我我的都嘗試過,出現的一些坑,確定要寫出來分享給你們。
1.庫衝突問題。這也是我爲何兩種方案都嘗試的緣由。我在首先嚐試的JSPatch,發現Aspects這個框架ide
https://github.com/steipete/Aspects 作一些AOP變成的hook庫。
同時使用了_objc_msgForward這個IMP,上面是Aspects,下面是JSPatch。我抱着僥倖的內心,雖然已經認識到WaxPatch極有可能也是如此實現的,去嘗試了WaxPatch,果不其然,依舊不行,看最下面的代碼,就是lua的。lua
static BOOL aspect_isMsgForwardIMP(IMP impl) { return impl == _objc_msgForward #if !defined(__arm64__) || impl == (IMP)_objc_msgForward_stret #endif ; }
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription) { SEL selector = NSSelectorFromString(selectorName); if (!typeDescription) { Method method = class_getInstanceMethod(cls, selector); typeDescription = (char *)method_getTypeEncoding(method); } IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL; IMP msgForwardIMP = _objc_msgForward; #if !defined(__arm64__) if (typeDescription[0] == '{') { //In some cases that returns struct, we should use the '_stret' API: //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription. NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription]; if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) { msgForwardIMP = (IMP)_objc_msgForward_stret; } } #endif class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) { IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@"); class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@"); } #pragma clang diagnostic pop if (class_respondsToSelector(cls, selector)) { NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName]; SEL originalSelector = NSSelectorFromString(originalSelectorName); if(!class_respondsToSelector(cls, originalSelector)) { class_addMethod(cls, originalSelector, originalImp, typeDescription); } } NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; SEL JPSelector = NSSelectorFromString(JPSelectorName); _initJPOverideMethods(cls); _JSOverideMethods[cls][JPSelectorName] = function; class_addMethod(cls, JPSelector, msgForwardIMP, typeDescription); } static BOOL isMethodReplacedByInvocation(id klass, SEL selector){ Method selectorMethod = class_getInstanceMethod(klass, selector); IMP imp = method_getImplementation(selectorMethod); #if defined(__arm64__) return imp == _objc_msgForward; #else return imp == _objc_msgForward || imp == (IMP)_objc_msgForward_stret; #endif } static BOOL isMethodReplacedByInvocation(id klass, SEL selector){ Method selectorMethod = class_getInstanceMethod(klass, selector); IMP imp = method_getImplementation(selectorMethod); #if defined(__arm64__) return imp == _objc_msgForward; #else return imp == _objc_msgForward || imp == (IMP)_objc_msgForward_stret; #endif }
static void replaceMethodAndGenerateORIG(id klass, SEL selector, IMP newIMP){ Method selectorMethod = class_getInstanceMethod(klass, selector); const char *typeDescription = method_getTypeEncoding(selectorMethod); IMP prevImp = class_replaceMethod(klass, selector, newIMP, typeDescription); if(prevImp == newIMP){ // NSLog(@"Repetition replace but, never mind"); return ; } const char *selectorName = sel_getName(selector); char newSelectorName[strlen(selectorName) + 10]; strcpy(newSelectorName, WAX_ORIGINAL_METHOD_PREFIX); strcat(newSelectorName, selectorName); SEL newSelector = sel_getUid(newSelectorName); if(!class_respondsToSelector(klass, newSelector)) { BOOL res = class_addMethod(klass, newSelector, prevImp, typeDescription); // NSLog(@"res=%d", res); } }
既然衝突了,就必須解決衝突。魚與熊掌,兩者不可得兼,舍魚而取熊掌。只能把 Aspects幹掉了。
但是Aspects,實現的方法如何去替換呢?我就直接用了method swizzle。簡單的實現了下Aspects的功能,可是沒那麼優雅。debug
@implementation UIViewController (AOP) + (void)initialize { Method ori_Method = class_getInstanceMethod([UIViewController class], @selector(viewDidAppear:)); Method my_Method = class_getInstanceMethod([UIViewController class], @selector(aop_viewDidAppear:)); method_exchangeImplementations(ori_Method, my_Method); } - (void)aop_viewDidAppear:(BOOL)animated { [self aop_viewDidAppear:animated]; NSLog(@"------hook"); } @end
對,就是簡單的hook一下。不過你要考慮清楚,對同一個方法hook幾回,最後的執行順序問題。code
ReactiveCocoa這個框架也可能存在相似問題,可能沒那麼好解決了。這個時候可能要作一些取捨,若是你是leader的話,可能要制定下規範來避免這個問題, 什麼方法不能夠用,要如何hook等等,暫時我也沒有太好的解決方案。