iOS热修复之LYFix(基于aspects)

简介

LYFix Demo

LYFix 基于 Aspects (做了一点改动),NSInvocation实现的简单hotfix方案。
LYFix 通过下发js代码提供以下功能:

  • 在任意方法前后加入代码 (基于aspects)。
  • 替换任意方法的实现 (基于aspects)。
  • 调用任意方法 (基于NSInvocation)。
  • 修改方法参数 (基于NSInvocation)。
  • 修改方法返回值 (基于NSInvocation)。

更新

2018年09月17日11:13:26

Aspects: A method can only be hooked once per class hierarchy.
增加+ (NSString *)classNameWithInstance:(NSObject *)instance;方法
通过在js里判断是否是同一个类名作出相应的处理。

fixMethod("Test", "changePrames:", 2, function(instance, invocation, arg) {
    var className = runMethod("LYComAPI", "classNameWithInstance:", instance);
    if (className == "Test") {
        runLog("11111");
        var params = new Array();
        params[0] = "newParams";
        setInvocationArguments(invocation, params);
    } else {
        runLog("00000");
    }
});

使用

  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 << 3 /// 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:", 1, function(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拓展(提供修改参数,返回值等)

//
//  NSInvocation+LYAddtion.m
//  LYFix
//
//  Created by xly on 2018/7/26.
//  Copyright © 2018年 Xly. All rights reserved.
//

#import "NSInvocation+LYAddtion.h"
#import 
#import 

@implementation NSInvocation (LYAddtion)

- (void)setReturnValue_obj:(id)returnValue_obj {
    objc_setAssociatedObject(self, @selector(returnValue_obj), returnValue_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
#define SET_RETURN_VALUE_OBJ_TYPE_METHOD(type, method) \
else if(strcmp(returnType, @encode(type)) == 0) \
{ \
type result = [returnValue_obj method]; \
[self setReturnValue:&result]; \
}
#define SET_RETURN_VALUE_OBJ_TYPE(type) SET_RETURN_VALUE_OBJ_TYPE_METHOD(type, type ## Value)
    const char * returnType = self.methodSignature.methodReturnType;

    if(strcmp(returnType, @encode(id)) == 0) {
        [self setReturnValue:&returnValue_obj];
    } else if(strcmp(returnType, "?") == 0) {
        void * returnValuePointer = [returnValue_obj pointerValue];
        [self setReturnValue:&returnValuePointer];
    } else if(strcmp(returnType, "*") == 0) {
        void * returnValuePointer = [returnValue_obj pointerValue];
        [self setReturnValue:&returnValuePointer];
    } else if(strcmp(returnType, ":") == 0) {
        SEL returnValuePointer = [returnValue_obj pointerValue];
        [self setReturnValue:&returnValuePointer];
    }
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(BOOL, boolValue)
    SET_RETURN_VALUE_OBJ_TYPE(bool)
    SET_RETURN_VALUE_OBJ_TYPE(char)
    SET_RETURN_VALUE_OBJ_TYPE(short)
    SET_RETURN_VALUE_OBJ_TYPE(int)
    SET_RETURN_VALUE_OBJ_TYPE(long)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(long long, longLongValue)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(unsigned char, unsignedCharValue)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(unsigned short, unsignedShortValue)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(unsigned int, unsignedIntValue)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(unsigned long, unsignedLongValue)
    SET_RETURN_VALUE_OBJ_TYPE_METHOD(unsigned long long, unsignedLongLongValue)
    SET_RETURN_VALUE_OBJ_TYPE(float)
    SET_RETURN_VALUE_OBJ_TYPE(double)
}


- (id)returnValue_obj {

    NSMethodSignature *sig = self.methodSignature;
    NSInvocation *inv = self;
    NSUInteger length = [sig methodReturnLength];
    if (length == 0) return nil;
    
    char *type = (char *)[sig methodReturnType];
    while (*type == 'r' || // const
           *type == 'n' || // in
           *type == 'N' || // inout
           *type == 'o' || // out
           *type == 'O' || // bycopy
           *type == 'R' || // byref
           *type == 'V') { // oneway
        type++; // cutoff useless prefix
    }
    
#define return_with_number(_type_) \
do { \
_type_ ret; \
[inv getReturnValue:&ret]; \
return @(ret); \
} while (0)
    
    switch (*type) {
        case 'v': return nil; // void
        case 'B': return_with_number(bool);
        case 'c': return_with_number(char);
        case 'C': return_with_number(unsigned char);
        case 's': return_with_number(short);
        case 'S': return_with_number(unsigned short);
        case 'i': return_with_number(int);
        case 'I': return_with_number(unsigned int);
        case 'l': return_with_number(int);
        case 'L': return_with_number(unsigned int);
        case 'q': return_with_number(long long);
        case 'Q': return_with_number(unsigned long long);
        case 'f': return_with_number(float);
        case 'd': return_with_number(double);
        case 'D': { // long double
            long double ret;
            [inv getReturnValue:&ret];
            return [NSNumber numberWithDouble:ret];
        };
            
        case '@': { // id
            void *ret;
            [inv getReturnValue:&ret];
            return (__bridge id)(ret);
        };
            
        case '#': { // Class
            Class ret = nil;
            [inv getReturnValue:&ret];
            return ret;
        };
            
        default: { // struct / union / SEL / void* / unknown
            const char *objCType = [sig methodReturnType];
            char *buf = calloc(1, length);
            if (!buf) return nil;
            [inv getReturnValue:buf];
            NSValue *value = [NSValue valueWithBytes:buf objCType:objCType];
            free(buf);
            return value;
        };
    }
#undef return_with_number
}


- (void)setMyArgument:(id)obj atIndex:(NSInteger)argumentIndex {
//    objc_setAssociatedObject(self, &argumentIndex, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
#define set_with_args_index(_index_, _type_, _sel_) \
do { \
_type_ arg; \
arg = [obj _sel_]; \
[self setArgument:&arg atIndex:_index_]; \
} while(0)
    
#define set_with_args_struct(_dic_, _struct_, _param_, _key_, _sel_) \
do { \
if (_dic_ && [_dic_ isKindOfClass:[NSDictionary class]]) { \
if ([_dic_.allKeys containsObject:_key_]) { \
_struct_._param_ = [_dic_[_key_] _sel_]; \
} \
} \
} while(0)
    NSMethodSignature *sig = self.methodSignature;
    NSUInteger count = [sig numberOfArguments];
    NSInteger index = argumentIndex + 2;
    
    if (index >= count) {
        return;
    }
    
    char *type = (char *)[sig getArgumentTypeAtIndex:index];
    while (*type == 'r' ||  // const
           *type == 'n' ||  // in
           *type == 'N' ||  // inout
           *type == 'o' ||  // out
           *type == 'O' ||  // bycopy
           *type == 'R' ||  // byref
           *type == 'V') {  // oneway
        type++;             // cutoff useless prefix
    }
    
    BOOL unsupportedType = NO;
    switch (*type) {
        case 'v':   // 1:void
        case 'B':   // 1:bool
        case 'c':   // 1: char / BOOL
        case 'C':   // 1: unsigned char
        case 's':   // 2: short
        case 'S':   // 2: unsigned short
        case 'i':   // 4: int / NSInteger(32bit)
        case 'I':   // 4: unsigned int / NSUInteger(32bit)
        case 'l':   // 4: long(32bit)
        case 'L':   // 4: unsigned long(32bit)
        { // 'char' and 'short' will be promoted to 'int'
            set_with_args_index(index, int, intValue);
        } break;
            
        case 'q':   // 8: long long / long(64bit) / NSInteger(64bit)
        case 'Q':   // 8: unsigned long long / unsigned long(64bit) / NSUInteger(64bit)
        {
            set_with_args_index(index, long long, longLongValue);
        } break;
            
        case 'f': // 4: float / CGFloat(32bit)
        {
            set_with_args_index(index, float, floatValue);
        } break;
            
        case 'd': // 8: double / CGFloat(64bit)
        case 'D': // 16: long double
        {
            set_with_args_index(index, double, doubleValue);
        } break;
            
        case '*': // char *
        {
            NSString *arg = obj;
            if ([arg isKindOfClass:[NSString class]]) {
                const void *c = [arg UTF8String];
                [self setArgument:&c atIndex:index];
            }
        } break;
            
        case '#': // Class
        {
            NSString *arg = obj;
            if ([arg isKindOfClass:[NSString class]]) {
                Class klass = NSClassFromString(arg);
                if (klass) {
                    [self setArgument:&klass atIndex:index];
                }
            }
        } break;
            
        case '@': // id
        {
            id arg = obj;
            [self setArgument:&arg atIndex:index];
        } break;
            
        case '{': // struct
        {
            if (strcmp(type, @encode(CGPoint)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    CGPoint point = CGPointFromString(obj);
                    [self setArgument:&point atIndex:index];
                } else {
                    CGPoint point = [obj CGPointValue];
                    [self setArgument:&point atIndex:index];
                }
            } else if (strcmp(type, @encode(CGSize)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    CGSize size = CGSizeFromString(obj);
                    [self setArgument:&size atIndex:index];
                } else {
                    CGSize size = [obj CGSizeValue];
                    [self setArgument:&size atIndex:index];
                }
            } else if (strcmp(type, @encode(CGRect)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    CGRect rect = CGRectFromString(obj);
                    [self setArgument:&rect atIndex:index];
                } else {
                    CGRect rect;
                    rect = [obj CGRectValue];
                    [self setArgument:&rect atIndex:index];
                }
            } else if (strcmp(type, @encode(CGVector)) == 0) {
                CGVector vector = {0};
                
              
                NSDictionary *dict = obj;
                set_with_args_struct(dict, vector, dx, @"dx", doubleValue);
                set_with_args_struct(dict, vector, dy, @"dy", doubleValue);
                [self setArgument:&vector atIndex:index];
            } else if (strcmp(type, @encode(CGAffineTransform)) == 0) {
                CGAffineTransform form = {0};
                
                NSDictionary *dict = obj;
                set_with_args_struct(dict, form, a, @"a", doubleValue);
                set_with_args_struct(dict, form, b, @"b", doubleValue);
                set_with_args_struct(dict, form, c, @"c", doubleValue);
                set_with_args_struct(dict, form, d, @"d", doubleValue);
                set_with_args_struct(dict, form, tx, @"tx", doubleValue);
                set_with_args_struct(dict, form, ty, @"ty", doubleValue);
                [self setArgument:&form atIndex:index];
            } else if (strcmp(type, @encode(CATransform3D)) == 0) {
                CATransform3D form3D = {0};
                
                NSDictionary *dict = obj;
                set_with_args_struct(dict, form3D, m11, @"m11", doubleValue);
                set_with_args_struct(dict, form3D, m12, @"m12", doubleValue);
                set_with_args_struct(dict, form3D, m13, @"m13", doubleValue);
                set_with_args_struct(dict, form3D, m14, @"m14", doubleValue);
                set_with_args_struct(dict, form3D, m21, @"m21", doubleValue);
                set_with_args_struct(dict, form3D, m22, @"m22", doubleValue);
                set_with_args_struct(dict, form3D, m23, @"m23", doubleValue);
                set_with_args_struct(dict, form3D, m24, @"m24", doubleValue);
                set_with_args_struct(dict, form3D, m31, @"m31", doubleValue);
                set_with_args_struct(dict, form3D, m32, @"m32", doubleValue);
                set_with_args_struct(dict, form3D, m33, @"m33", doubleValue);
                set_with_args_struct(dict, form3D, m34, @"m34", doubleValue);
                set_with_args_struct(dict, form3D, m41, @"m41", doubleValue);
                set_with_args_struct(dict, form3D, m42, @"m42", doubleValue);
                set_with_args_struct(dict, form3D, m43, @"m43", doubleValue);
                set_with_args_struct(dict, form3D, m44, @"m44", doubleValue);
                [self setArgument:&form3D atIndex:index];
            } else if (strcmp(type, @encode(NSRange)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    NSRange range = NSRangeFromString(obj);
                    [self setArgument:&range atIndex:index];
                } else {
                    NSRange range = [obj rangeValue];
                    [self setArgument:&range atIndex:index];
                }
            } else if (strcmp(type, @encode(UIOffset)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    UIOffset offset = UIOffsetFromString(obj);
                    [self setArgument:&offset atIndex:index];
                } else {
                    UIOffset offset = [obj UIOffsetValue];
                    [self setArgument:&offset atIndex:index];
                }
            } else if (strcmp(type, @encode(UIEdgeInsets)) == 0) {
                if ([obj isKindOfClass:NSString.class]) {
                    UIEdgeInsets insets = UIEdgeInsetsFromString(obj);
                    [self setArgument:&insets atIndex:index];
                } else {
                    UIEdgeInsets insets = [obj UIEdgeInsetsValue];
                    [self setArgument:&insets atIndex:index];
                }
            } else {
                unsupportedType = YES;
            }
        } break;
            
        case '^': // pointer
        {
            unsupportedType = YES;
        } break;
            
        case ':': // SEL
        {
            unsupportedType = YES;
        } break;
            
        case '(': // union
        {
            unsupportedType = YES;
        } break;
            
        case '[': // array
        {
            unsupportedType = YES;
        } break;
            
        default: // what?!
        {
            unsupportedType = YES;
        } break;
    }
    [self retainArguments];
    NSAssert(!unsupportedType, @"arg unsupportedType");
}

- (id)myArgumentAtIndex:(NSUInteger)index {
    NSUInteger count = [self.methodSignature numberOfArguments];
    NSInteger argumentIndex = index + 2;
    if (argumentIndex >= count) {
        return nil;
    }
    return [self argumentAtIndex:argumentIndex];
}

- (void)setArguments:(NSArray *)arguments {
    for (int index = 0; index < arguments.count; index++) {
        [self setMyArgument:arguments[index] atIndex:index];
    }
    
}

- (NSArray *)arguments {
    NSMutableArray *argumentsArray = [NSMutableArray array];
    for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
        [argumentsArray addObject:[self argumentAtIndex:idx] ?: NSNull.null];
    }
    return [argumentsArray copy];
}

- (id)argumentAtIndex:(NSUInteger)index {
    const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
    // Skip const type qualifier.
    if (argType[0] == _C_CONST) argType++;
    
#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
    if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
        __autoreleasing id returnObj;
        [self getArgument:&returnObj atIndex:(NSInteger)index];
        return returnObj;
    } else if (strcmp(argType, @encode(SEL)) == 0) {
        SEL selector = 0;
        [self getArgument:&selector atIndex:(NSInteger)index];
        return NSStringFromSelector(selector);
    } else if (strcmp(argType, @encode(Class)) == 0) {
        __autoreleasing Class theClass = Nil;
        [self getArgument:&theClass atIndex:(NSInteger)index];
        return theClass;
        // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
    } else if (strcmp(argType, @encode(char)) == 0) {
        WRAP_AND_RETURN(char);
    } else if (strcmp(argType, @encode(int)) == 0) {
        WRAP_AND_RETURN(int);
    } else if (strcmp(argType, @encode(short)) == 0) {
        WRAP_AND_RETURN(short);
    } else if (strcmp(argType, @encode(long)) == 0) {
        WRAP_AND_RETURN(long);
    } else if (strcmp(argType, @encode(long long)) == 0) {
        WRAP_AND_RETURN(long long);
    } else if (strcmp(argType, @encode(unsigned char)) == 0) {
        WRAP_AND_RETURN(unsigned char);
    } else if (strcmp(argType, @encode(unsigned int)) == 0) {
        WRAP_AND_RETURN(unsigned int);
    } else if (strcmp(argType, @encode(unsigned short)) == 0) {
        WRAP_AND_RETURN(unsigned short);
    } else if (strcmp(argType, @encode(unsigned long)) == 0) {
        WRAP_AND_RETURN(unsigned long);
    } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
        WRAP_AND_RETURN(unsigned long long);
    } else if (strcmp(argType, @encode(float)) == 0) {
        WRAP_AND_RETURN(float);
    } else if (strcmp(argType, @encode(double)) == 0) {
        WRAP_AND_RETURN(double);
    } else if (strcmp(argType, @encode(BOOL)) == 0) {
        WRAP_AND_RETURN(BOOL);
    } else if (strcmp(argType, @encode(bool)) == 0) {
        WRAP_AND_RETURN(BOOL);
    } else if (strcmp(argType, @encode(char *)) == 0) {
        WRAP_AND_RETURN(const char *);
    } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
        __unsafe_unretained id block = nil;
        [self getArgument:&block atIndex:(NSInteger)index];
        return [block copy];
    } else {
        NSUInteger valueSize = 0;
        NSGetSizeAndAlignment(argType, &valueSize, NULL);
        
        unsigned char valueBytes[valueSize];
        [self getArgument:valueBytes atIndex:(NSInteger)index];
        
        return [NSValue valueWithBytes:valueBytes objCType:argType];
    }
    return nil;
#undef WRAP_AND_RETURN
}

@end


感谢

LYFix Demo
LBYFix-依赖Aspects的轻量级低风险的 iOS Hotfix 方案
YYKit
Aspects

还没有应用到项目中,不晓得是否能通过审核。
就算使用肯定也要做相应的安全处理、切记。

个人开发的第一个小程序(计算器UX),大家多多支持


iOS热修复之LYFix(基于aspects)_第1张图片
计算器.jpg

你可能感兴趣的:(iOS热修复之LYFix(基于aspects))