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等等,暂时我也没有太好的解决方案。