Aspects 风险很小的热修复方案(JSPatch 可以任意修改, Aspects 范围 较小) Runtime Class 反射创建 // 1 NSClassFromString(@"NSObject"); // 2 objc_getClass("NSObject"); SEL 反射创建 // 1 @selector(init); // 2 sel_registerName("init"); // 3 NSSelectorFromString(@"init"); 方法替换 static void cc_forwardInvocation(id slf, SEL sel, NSInvocation *invocation) { // do what you want to do } class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)cc_forwardInvocation, "v@:@"); 方法新增 Class tClass = NSClassFromString(@"UIViewController"); SEL selector = NSSelectorFromString(@"viewDidLoad"); Method targetMethod = class_getInstanceMethod(tClass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); const char *typeEncoding = method_getTypeEncoding(targetMethod); SEL aliasSelector = NSSelectorFromString([@"cc" stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]); class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding); 新类创建 Class cls = objc_allocateClassPair([NSObject class], “CCObject”, 0); objc_registerClassPair(cls); 消息转发 // 1. 正常转发 + (BOOL)resolveClassMethod:(SEL)sel + (BOOL)resolveInstanceMethod:(SEL)sel - (id)forwardingTargetForSelector:(SEL)aSelector - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector - (void)forwardInvocation:(NSInvocation *)anInvocation // 2. 自定义转发 void _objc_msgForward(void /* id receiver, SEL sel, ... */ ) Method Invoke 的几种方式 调用函数的几种方式 : 常规调用 反射调用 objc_msgSend C 函数调用 NSInvocation 调用 // 常规调用 People *people = [[People alloc] init]; [people helloWord]; // 反射调用 Class cls = NSClassFromString(@"People"); id obj = [[cls alloc] init]; [obj performSelector:NSSelectorFromString(@"helloWord")]; // objc_msgSend ((void(*)(id, SEL))objc_msgSend)(people, sel_registerName("helloWord")); // C 函数调用 Method initMethod = class_getInstanceMethod([People class], @selector(helloWord)); IMP imp = method_getImplementation(initMethod); ((void (*) (id, SEL))imp)(people, @selector(helloWord)); // NSInvocation 调用 NSMethodSignature *sig = [[People class] ; //类签名 instanceMethodSignatureForSelector:sel_registerName("helloWord")]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = people; invocation.selector = sel_registerName("helloWord"); [invocation invoke]; Hook 流程 1.检查 selector 是否可以替换,里面涉及一些黑名单等判断 2.获取 AspectsContainer,如果为空则创建并绑定目标类 3.创建 AspectIdentifier,引用自定义实现(block)和 AspectOptions 等信息 4.将目标类 forwardInvocation: 方法替换为自定义方法 5.目标类新增一个带有aspects_前缀的方法,新方法(aliasSelector)实现跟目 标方法相同 6.将目标方法实现替换为 _objc_msgForward // 将目标类 **forwardInvocation:** 方法替换为自定义方法 IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@"); if (originalImplementation) { class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); } // 目标类新增一个带有` aspects_`前缀的方法,新方法(aliasSelector)实现 跟目标方法相同 Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); const char *typeEncoding = method_getTypeEncoding(targetMethod); SEL aliasSelector = NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]); class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding); // 将目标方法实现替换为 `_objc_msgForward` class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); 热修复-方法前,方法后 插入代码,方法替换等 修改前原生代码 1.首先假如我们项目中写有如下代码: MightyCrash *mc = [[MightyCrash alloc] init]; [mc divideUsingDenominator: 0]; divideUsingDenominator方法内部实现如下: - (float)divideUsingDenominator:(NSInteger)denominator { return 1.f / denominator; } 问题所在: 因为调用方法时候我们传入的值为0,所以除以0会出现问题 热修复解决: didFinishLaunchingWithOptions 方法中 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //初始化环境 [Felix fixIt]; //后台返回字符串格式 NSString *fixScriptString = @" \ fixInstanceMethodReplace('MightyCrash', 'divideUsingDenominator:', function(instance, originInvocation, originArguments){ \ if (originArguments[0] == 0) { \ console.log('zero goes here'); \ } else { \ runInvocation(originInvocation); \ } \ }); \ \ "; //修复 [Felix evalString:fixScriptString]; return YES; } 原理 1. 首先第一步的[Felix fixIt];执行结果,会生成一个全局的JSContext对象来为执 行JS方法提供环境 + (JSContext *)context { static JSContext *_context; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _context = [[JSContext alloc] init]; [_context setExceptionHandler:^(JSContext *context, JSValue *value) { NSLog(@"Oops: %@", value); }]; }); return _context; } 2. 同时,使用匿名函数的方式包装OC方法: [self context][@"fixInstanceMethodBefore"] = ^(NSString *instanceName, NSString *selectorName, JSValue *fixImpl) { [self _fixWithMethod:NO aspectionOptions:AspectPositionBefore instanceName:instanceName selectorName:selectorName fixImpl:fixImpl]; }; 包装的OC方法是什么呢?我们来看实现: + (void)_fixWithMethod:(BOOL)isClassMethod aspectionOptions: (AspectOptions)option instanceName:(NSString *)instanceName selectorName:(NSString *)selectorName fixImpl:(JSValue *)fixImpl { Class klass = NSClassFromString(instanceName); if (isClassMethod) { klass = object_getClass(klass); } SEL sel = NSSelectorFromString(selectorName); [klass aspect_hookSelector:sel withOptions:option usingBlock:^(id<AspectInfo> aspectInfo){ [fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]]; } error:nil]; } 3. 字符串fixScriptString, 该字符串其实是一个JS方法 : 方法传入的参数1是MightyCrash类,即我们要来hack的目标类 方法传入的参数2是divideUsingDenominator,即我们要来hack的目标类的方 方法传入的参数1是一个function,即我们要替换的方法实现 4. 第三步 [Felix evalString:fixScriptString] 会在全局的JSContent环境中, 执行下面绿色部分fixInstanceMethodReplaceJS 方法 , 从而执行对应OC方法 OC方法使用Method Swizzling黑魔法完成了目标执行函数的替换,即 MightyCrash类中的 - (float)divideUsingDenominator:(NSInteger)denominator; 方法替换为 [fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]]; 5. 最后,当执行原生方法[mc divideUsingDenominator:0]; 时 其实是调用了 [fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];方法, 该方法会执行第二步声明的JS方法的第三个function参数,即执行: function(instance, originInvocation, originArguments){ if (originArguments[0] == 0) { console.log('zero goes here'); } else { runInvocation(originInvocation); } LBYFix 使用 https://www.jianshu.com/p/d4574a4268b3 初始化LBYFix [LBYFix fixIt]; 替换方法实现 NSString *jsString = @"fixMethod('LBYFixDemo', 'instanceMightCrash:', 1, false, \ function(instance, originInvocation, originArguments) { \ if (originArguments[0] == null) { \ runErrorBranch('LBYFixDemo', 'instanceMightCrash'); \ } else { \ runInvocation(originInvocation); \ } \ }); \ "; [LBYFix evalString:jsString]; 在方法前插入代码 NSString *jsString = @"fixMethod('LBYFixDemo', 'runBeforeInstanceMethod', 2, false, \ function(){ \ runInstanceMethod('LBYFixDemo', 'beforeInstanceMethod:param2:', new Array('LBYFix', 888)); \ });"; [LBYFix evalString:jsString]; 在方法后插入代码 NSString *jsString = @"fixMethod('LBYFixDemo2', 'runAfterClassMethod', 0, true, \ function(){ \ runClassMethod('LBYFixDemo2', 'afterClassMethod:param2:', new Array('LBYFix', 999)); \ }); \ "; [LBYFix evalString:jsString]; 执行没有参数的方法 NSString *jsString = @"runInstanceMethod('LBYFixDemo3', 'instanceMethodHasNoParams')"; [LBYFix evalString:jsString]; 执行带多个参数的方法 NSString *jsString = @"runInstanceMethod('LBYFixDemo3', 'instanceMethodHasMultipleParams:size:rect:', new Array({x: 1.1, y: 2.2}, {width: 3.3, height: 4.4}, {origin: {x: 5.5, y: 6.6}, size: {width: 7.7, height: 8.8}}))\ "; [LBYFix evalString:jsString]; 上面js代码的意思是调用LBYFixDemo3类的 instanceMethodHasMultipleParams:size:rect:实例方法,并通过数组传入参 数。 原理解析等等