iOS热修复之LYFix(基于aspects)

百家 作者:iOS开发 2018-08-31 11:15:59

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!


作者:iOS开发Go

链接:https://www.jianshu.com/p/2fd3018955e2

iOS开发整理发布,转载请联系作者获得授权


简介


LYFix Demo


LYFix 基于 Aspects (做了一点改动),NSInvocation实现的简单hotfix方案。

LYFix 通过下发js代码提供以下功能:


  • 在任意方法前后加入代码 (基于aspects)。

  • 替换任意方法的实现 (基于aspects)。

  • 调用任意方法 (基于NSInvocation)。

  • 修改方法参数 (基于NSInvocation)。

  • 修改方法返回值 (基于NSInvocation)。


使用


1. 初始化


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [LYFix Fix];
    return YES;
}


使用的aspects的枚举


typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.

    AspectOptionAutomaticRemoval = 1 < < /// Will remove the hook after the first execution.
};


2.方法前插入代码


fixMethod('Test''runBeforeInstanceMethod'2,
          function(){
          runMethod('Test''log');
          });


3.方法后插入代码


fixMethod('Test''runAfterInstanceMethod'0,
          function(){
          runMethod('Test''log');
          });


4.替换方法


fixMethod('Test''runInsteadClassMethod'1,
          function(){
          runMethod('Test''log');
          });


5.修复方法


fixMethod('Test''instanceMethodCrash:'1,
          function(instance, originInvocation, originArguments{
          if (originArguments[0] == null) {
          runError('Test''instanceMethodCrash');
          } else {
          runInvocation(originInvocation);
          }
          });


6.修改参数


fixMethod('Test','runWithParams:',1,function(instance,invocation,arg){
          var array = new Array('123');
          var arr = new Array(array);
          setInvocationArguments(invocation,arr);
          runInvocation(invocation);
          });


7.修改返回值


fixMethod("Test""changeReturnValue:"1function(instance, invocation, arg{
          runInvocation(invocation);
          setInvocationReturnValue(invocation,'new returnValue');
          });


源码


初始化方法,有其他方法供js调用还可以在此继续添加。


+ (void)Fix {

    JSContext *context = [self context];
    context[@"fixMethod"] = ^(NSString *className, NSString *selectorName, AspectOptions options, JSValue *fixImp) {
        [self fixWithClassName:className opthios:options selector:selectorName fixImp:fixImp];
    };
    context[@"runMethod"] = ^id(NSString * className, NSString *selectorName, id arguments) {
        id obj = [self runWithClassname:className selector:selectorName arguments:arguments];
        return obj;
    };

    context[@"runInstanceMethod"] = ^id(id instance, NSString *selectorName, id arguments) {
        id obj = [self runWithInstance:instance selector:selectorName arguments:arguments];
        return obj;
    };

    context[@"runClassMethod"] = ^id(NSString * className, NSString *selectorName, id arguments) {
        id obj = [self runWithClassname:className selector:selectorName arguments:arguments];
        return obj;
    };

    context[@"runInvocation"] = ^(NSInvocation *invocation) {
        [invocation invoke];
    };

    context[@"setInvocationArguments"] = ^(NSInvocation *invocation, id arguments) {
        if ([arguments isKindOfClass:NSArray.class]) {
            invocation.arguments = arguments;
        } else {
            [invocation setMyArgument:arguments atIndex:0];
        }
    };
    context[@"setInvocationArgumentAtIndex"] = ^(NSInvocation *invocation, id argument,NSInteger index) {
        [invocation setMyArgument:argument atIndex:index];
    };
    context[@"setInvocationReturnValue"] = ^(NSInvocation *invocation, id returnValue) {
        invocation.returnValue_obj = returnValue;
    };
    context[@"runError"] = ^(NSString *instanceName, NSString *selectorName) {
        NSLog(@"runError: instanceName = %@, selectorName = %@", instanceName, selectorName);
    };
    context[@"runLog"] = ^(id logs) {
        NSLog(@"xly--%@",logs);
    };
}


基于aspects前插、后插、替换


+ (void)fixWithClassName:(NSString *)className opthios:(AspectOptions)options selector:(NSString *)selector fixImp:(JSValue *)fixImp {
    Class cla = NSClassFromString(className);
    SEL sel = NSSelectorFromString(selector);
    if ([cla instancesRespondToSelector:sel]) {

    } else if ([cla respondsToSelector:sel]){
        cla = object_getClass(cla);
    } else {
        return;
    }
    [cla aspect_hookSelector:sel withOptions:options usingBlock:^(id aspectInfo) {
        [fixImp callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];
    } error:nil];
}


基于NSInvocation方法调用


+ (id)runWithInstance:(id)instance selector:(NSString *)selector arguments:(NSArray *)arguments {
    if (!instance) {
        return nil;
    }
    SEL sel = NSSelectorFromString(selector);

    NSLog(@"xly--%@",[instance class]);
    if ([instance isKindOfClass:JSValue.class]) {
        instance = [instance toObject];
    }
    NSMethodSignature *signature = [instance methodSignatureForSelector:sel];
    if (!signature) {
        return nil;
    }
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.selector = sel;
    invocation.arguments = arguments;
    [invocation invokeWithTarget:instance];
    return invocation.returnValue_obj;
}

+ (id)runWithClassname:(NSString *)className selector:(NSString *)selector arguments:(NSArray *)arguments {
    Class cla = NSClassFromString(className);
    SEL sel = NSSelectorFromString(selector);
    if ([cla instancesRespondToSelector:sel]) {
        id instance = [[cla alloc] init];
        NSMethodSignature *signature = [instance methodSignatureForSelector:sel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.selector = sel;
        invocation.arguments = arguments;
        [invocation invokeWithTarget:instance];
        return invocation.returnValue_obj;
    } else if ([cla respondsToSelector:sel]){
        NSMethodSignature *signature = [cla methodSignatureForSelector:sel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.selector = sel;
        invocation.arguments = arguments;
        [invocation invokeWithTarget:cla];
        return invocation.returnValue_obj;
    } else {
        return nil;
    }
}


NSInvocation拓展(提供修改参数,返回值等)


【点击成为源码大神】

▼点击「阅读原文」进入程序员商城

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接