iOS应用动态部署方案

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

相关文章
相关标签/搜索