JSPatch源码解析

作者: SemyonXu | 来源:发表于2017-07-18 10:30 被阅读559次

    JSPatch一款优秀的热更新框架,最近由于各种原因被苹果封杀了,虽然我们开发者暂时不能使用这种方式来进行来热更新,但是代码还是值得我们好好学习一下的,尤其是里面对于Runtime的使用,我们可以把它当做是Runtime的实践教材。

    当然热更新的方式还有很多,Weex,React Native, Hybrid ... 好像苹果现在并没有禁止JavaScriptCore引擎下的代码下发。

    其实框架的实现原理,作者已经写的很详细了,本文主要是从源码的角度来解析实现过程。

    下面根据调用流程来看一下,具体的是怎么实现的。
    本文JS代码的调试是使用的Safari,具体步骤:
    Safari->开发->Simulator->选择当前执行的js

    使用方式

    举个栗子,比如有一个bug:

    - (IBAction)btnClick:(id)sender {
        NSArray *arrTest = @[@"1"];
        @try {
            NSString *strCrash = [arrTest objectAtIndex:2];
            NSLog(@"strCrash %@", strCrash);
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Click" message:@"Success" delegate:nil cancelButtonTitle:@"Yes" otherButtonTitles:nil, nil];
            [alert show];
        } @catch (NSException *exception) {
            NSLog(@"exception is %@", exception);
        } @finally {
            NSLog(@"finally");
        }
    }
    

    经典的数组越界问题,当然这里添加了try catch并不会崩溃,但是会抛出异常。

    使用JSPatch下发main.js代码如下:

    require("UIAlertView");
    
    defineClass("ViewController", {
    btnClick: function(sender) {
    var arrTest = ["arrTest"];
    var strCrash = arrTest[0];
    
    var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
        alert.show();
    }
    }, {}, {});
    
    

    当然我们现在没有JS代码下发的逻辑,所以模拟一下JS代码的执行,main.js先加到工程中:

    - (void)testJSPatch {
        NSString *strJsPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
        NSLog(@"strJsPath %@", strJsPath);
        JSValue *resultValue = [JPEngine evaluateScriptWithPath:strJsPath];
        NSLog(@"resultValue %@", resultValue);
    }
    

    追踪代码

    首先我们调用的是JPEngine类的evaluateScriptWithPath方法

    + (JSValue *)evaluateScriptWithPath:(NSString *)filePath
    {
        _scriptRootDir = [filePath stringByDeletingLastPathComponent];
        return [self _evaluateScriptWithPath:filePath];
    }
    
    + (JSValue *)_evaluateScriptWithPath:(NSString *)filePath
    {
        NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 获取JS代码 字符串
        return [self _evaluateScript:script withSourceURL:[NSURL URLWithString:[filePath lastPathComponent]]];
    }
    
    + (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
    {
        if (!script || ![JSContext class]) {
            _exceptionBlock(@"script is nil");
            return nil;
        }
        [self startEngine]; // 开启引擎, 开始初始化一些参数
        
        if (!_regex) {
            _regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
        }
        // js字符串代码格式转换:新增了try catch,防止崩溃,使用正则 替换方法__c(),方法的调用统一走__c()方法
        NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
        @try {
            if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
                return [_context evaluateScript:formatedScript withSourceURL:resourceURL]; // 调用JS
            } else {
                return [_context evaluateScript:formatedScript];
            }
        }
        @catch (NSException *exception) {
            _exceptionBlock([NSString stringWithFormat:@"%@", exception]);
        }
        return nil;
    }
    

    这里获取到JS代码字符串之后,开始调用+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL 方法执行调用。

    方法逻辑:

    • 调用startEngine 方法(初始化参数,并调用JSPatch.js)

    • 使用正则替换替换所有方法调用为 __c() 的形式,并添加try-catch语句防止崩溃

    • 执行main.js代码,并使用_exceptionBlock抛出异常

    看一下startEngine方法

    + (void)startEngine
    {
        if (![JSContext class] || _context) { // 如果已经初始化_context就返回,不需要重新初始化
            return;
        }
        
        JSContext *context = [[JSContext alloc] init];
        
        // 提前注册JS方法调用,js调用对应方法的时候回调给OC方法
    #ifdef DEBUG
        context[@"po"] = ^JSValue*(JSValue *obj) {
            id ocObject = formatJSToOC(obj);
            return [JSValue valueWithObject:[ocObject description] inContext:_context];
        };
    
        context[@"bt"] = ^JSValue*() {
            return [JSValue valueWithObject:_JSLastCallStack inContext:_context];
        };
    #endif
    
        context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
            return defineClass(classDeclaration, instanceMethods, classMethods); // 类名, 实例方法,类方法
        };
    
        context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) {
            return defineProtocol(protocolDeclaration, instProtocol,clsProtocol);
        };
        
        context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { // call instace method
            return callSelector(nil, selectorName, arguments, obj, isSuper);
        };
        context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
            return callSelector(className, selectorName, arguments, nil, NO);
        };
        
       ...
           
        context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
            NSLog(@"%@", exception);
            _exceptionBlock([NSString stringWithFormat:@"js exception: %@", exception]);
        };
        
        _nullObj = [[NSObject alloc] init];
        context[@"_OC_null"] = formatOCToJS(_nullObj);
        
        _context = context;
        
        // 初始化
        _nilObj = [[NSObject alloc] init];
        _JSMethodSignatureLock = [[NSLock alloc] init];
        _JSMethodForwardCallLock = [[NSRecursiveLock alloc] init];
        _registeredStruct = [[NSMutableDictionary alloc] init];
        _currInvokeSuperClsName = [[NSMutableDictionary alloc] init];
        
    #if TARGET_OS_IPHONE
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    #endif
        
        // 运行JSPatch.js代码
        NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"];
        if (!path) _exceptionBlock(@"can't find JSPatch.js");
        NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
        
        if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
            [_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];
        } else {
            [_context evaluateScript:jsCore];
        }
    }
    

    方法逻辑:

    • 提前注册JS调用OC的方法,具体如下
    _OC_defineClass, 定义类
    _OC_defineProtocol,定义协议
    _OC_callI,调用实例方法
    _OC_callC,调用类方法
    _OC_formatJSToOC,格式化JS为OC类型
    _OC_formatOCToJS,格式化OC为JS类型
    _OC_getCustomProps,获取属性get方法
    _OC_setCustomProps,获取属性set方法
    __weak,__weak属性
    __strong, __strong 属性
    _OC_superClsName,获取父类名
    resourcePath, 获取js文件路径
    dispatch_after,GCD延时调用
    dispatch_async_main,GCD异步主队列
    dispatch_sync_main,GCD同步主队列
    dispatch_async_global_queue,GCD全局队列
    releaseTmpObj,释放tmp对象
    _OC_log, log信息
    _OC_null, 空对象
    
    • 初始化一些变量 _nilObj,_JSMethodSignatureLock,_JSMethodForwardCallLock...

    • 加载JSPatch.js代码

    执行main.js代码的时候使用的是JSpatch.js里面的defineClass()方法

    看一下JSPatch.js中的defineClass()方法

      // declaration: 类名,父类,协议的描述,cls:supercls<protocol..>
      // properties: { 方法名:JS的方法实现 }
      // instMethods
      global.defineClass = function(declaration, properties, instMethods, clsMethods) {
        var newInstMethods = {}, newClsMethods = {}
        if (!(properties instanceof Array)) { // properties 不是数组,是字典类型: {方法名:方法体},直接赋值给 instMethods,然后置空
          clsMethods = instMethods
          instMethods = properties
          properties = null
        }
    
        /*
         逻辑:如果是属性那么使用数组[property1,property2], 再动态获取set,get方法然后放到instMethods字典中,也就是instMethods中存的是{方法名:方法实现}
         */
        if (properties) { // 此时 properties 应该是数组,那么处理OC属性Property相关,
          properties.forEach(function(name){
            if (!instMethods[name]) {
              instMethods[name] = _propertiesGetFun(name); // 设置property的get方法
            }
            var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1); // set方法
            if (!instMethods[nameOfSet]) {
              instMethods[nameOfSet] = _propertiesSetFun(name); // 设置property的set方法
            }
          });
        }
      
        // 获取真实的类名
        var realClsName = declaration.split(':')[0].trim()  // split 把一个字符串分割成字符串数组
    
        _formatDefineMethods(instMethods, newInstMethods, realClsName)
        _formatDefineMethods(clsMethods, newClsMethods, realClsName)
    
        var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods) // oc构造类,返回类名,父类名 返回值:@{@"cls": className, @"superCls": superClassName};
        var className = ret['cls']
        var superCls = ret['superCls']
    
        _ocCls[className] = {
          instMethods: {},
          clsMethods: {},
        }
    
        if (superCls.length && _ocCls[superCls]) {
          for (var funcName in _ocCls[superCls]['instMethods']) { // 如果父类中有这个实例方法,直接赋值给当前类
            _ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]
          }
          for (var funcName in _ocCls[superCls]['clsMethods']) {  // 如果父类中有这个类例方法,直接赋值给当前类
            _ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]
          }
        }
      
        // className: OC定义的类名,instMethods:实例方法{方法名:方法实现},instMethods:解析declaration获取的真实类名
        // 对js代码进行了一次包装,_wrapLocalMethod
        _setupJSMethod(className, instMethods, 1, realClsName)
        _setupJSMethod(className, clsMethods, 0, realClsName)
    
        return require(className) // 返回的是: {__clsName: 类名}
      }
    

    方法逻辑:

    • 处理传入的JS方法

      properties传入的不是数组类型,那么就是方法类型:{方法名:方法实现},直接赋值给instMethods,并置nil。

    • 处理属性方法数组

      properties传入的是数组类型,那么处理这些属性,动态生成set,get方法,使用的是Runtime的关联对象方法。

    • 格式化实例方法,类方法

      格式化类方法,实例方法,将方法封装成数组 [1, function()], 1 为 originMethod.length(var originMethod = methods[methodName] )根据OC代码来看应该是参数个数, function()方法对之前的方法进行了封装,封装方法跟下面的_setupJSMethod一样。

    • 调用OC的_OC_defineClass方法执行定义类

      下面解析这个OC方法,返回类型是:
      {"cls": className, "superCls": superClassName};

    • 如果本地缓存_ocCls中有父类,将继承所有父类方法。

    • 设置JS方法,进行了一次包装

    • 最后返回类名的字典:{__clsName: 类名}

    穿插中间调用JPEngine.m中的_OC_defineClass()方法

    context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
            return defineClass(classDeclaration, instanceMethods, classMethods); // 类名, 实例方法,类方法
        };
        
    static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
    {
        // classDeclaration: 原始的类名 + :父类 + '<>'协议
        // 通过runtime定义类,并返回类名,父类名
        NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
        
        NSString *className;
        NSString *superClassName;
        NSString *protocolNames; //  class:superclass<protocol> 扫描获取className,superClassName,protocolNames
        [scanner scanUpToString:@":" intoString:&className];
        if (!scanner.isAtEnd) {
            scanner.scanLocation = scanner.scanLocation + 1;
            [scanner scanUpToString:@"<" intoString:&superClassName];
            if (!scanner.isAtEnd) {
                scanner.scanLocation = scanner.scanLocation + 1;
                [scanner scanUpToString:@">" intoString:&protocolNames];
            }
        }
        
        if (!superClassName) superClassName = @"NSObject"; // 默认父类都是NSObject
        className = trim(className); // 去空格
        superClassName = trim(superClassName);
        
        NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
        
        Class cls = NSClassFromString(className);
        if (!cls) { // 转换不了此类,动态新增一个NSObject的子类
            Class superCls = NSClassFromString(superClassName);
            if (!superCls) {
                _exceptionBlock([NSString stringWithFormat:@"can't find the super class %@", superClassName]);
                return @{@"cls": className};
            }
            cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
            objc_registerClassPair(cls);
        }
        
        if (protocols.count > 0) { // 添加协议列表
            for (NSString* protocolName in protocols) {
                Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
                class_addProtocol (cls, protocol);
            }
        }
        
        for (int i = 0; i < 2; i ++) { // 第一次循环对类进行操作,第二次循环对元类进行操作
            BOOL isInstance = i == 0;
            JSValue *jsMethods = isInstance ? instanceMethods: classMethods; // 方法
            
            // instanceMethods 字典 key:方法名, value:数组(0:参数个数 1:js代码???)
            Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String); // 类名
            NSDictionary *methodDict = [jsMethods toDictionary];
            for (NSString *jsMethodName in methodDict.allKeys) {
                JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
                /*
                 1,function () {
                 try {
                 var args = _formatOCToJS(Array.prototype.slice.call(arguments))
                 var lastSelf = global.self
                 global.self = args[0]
                 if (global.self) global.self.__realClsName = realClsName
                 args.splice(0,1) // 删除第一个元素
                 var ret = originMethod.apply(originMethod, args)
                 global.self = lastSelf
                 return ret
                 } catch(e) {
                 _OC_catch(e.message, e.stack)
                 }
                 }
                 */
                int numberOfArg = [jsMethodArr[0] toInt32];
                NSString *selectorName = convertJPSelectorString(jsMethodName);
                
                if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) { // 这个地方一个参数可以添加一个':',如果是多个参数呢?怎么添加?
                    selectorName = [selectorName stringByAppendingString:@":"];
                }
                
                JSValue *jsMethod = jsMethodArr[1]; // 来自js代码
                if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) { // 类中含有这个方法,替换方法,跳转拦截调用
                    overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
                } else { // 类中没有这个方法, 可能是协议
                    BOOL overrided = NO;
                    for (NSString *protocolName in protocols) { // 如果有协议,全部添加
                        char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);
                        if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);
                        if (types) {
                            overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);
                            free(types);
                            overrided = YES;
                            break;
                        }
                    }
                    if (!overrided) {
                        if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {
                            NSMutableString *typeDescStr = [@"@@:" mutableCopy];
                            for (int i = 0; i < numberOfArg; i ++) {
                                [typeDescStr appendString:@"@"];
                            }
                            overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
                        }
                    }
                }
            }
        }
        
        // 新增两个方法: setProp:  getProp: forKey:
        class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
        class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
    
        return @{@"cls": className, @"superCls": superClassName};
    }
    

    方法逻辑:

    • 解析classDeclaration中的类名,父类,协议
    • 没有父类,默认是NSObject
    • 如果这个类不是系统类,调用runtime的objc_registerClassPair新建
    • 如果有protocols,动态添加协议列表
    • 添加实例方法,类方法

    双重for循环,第一次是对类操作,添加实例方法,第二次是对元类操作,添加类方法。

    • 遍历方法列表,获取jsMethodArr第0个位置:参数个数,为方法动态添加":"

    • 如果本类中,已经有这个方法,那么执行overrideMethod()方法替换,跳转拦截调用,下面解析

    • 如果本类中,没有这个方法,可能是协议,先获取协议方法的参数类型typeDescription,再执行overrideMethod()方法

    • 如果不是本类中的方法,也不是协议,那么需要获取到这个方法的参数类型typeDescription,再执行overrideMethod()

    • 为类新增set,get方法,统一使用关联对象关联property

    • 返回类名,父类名

    overrideMethod()到底做了些什么?

    // 方法替换方法 _objc_msgForward,所有的方法全部都跳转到拦截调用
    /*
     * 新增了一个ORIGFunc方法,方法实现是原来的方法实现,添加到本类中
     * 新增一个JSFunc名字, 将方法名添加_JP前缀作为key,将JS代码作为value存入_JSOverideMethods中, 为以后JS调用做存储
     */
    static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
    {
        SEL selector = NSSelectorFromString(selectorName);
        
        if (!typeDescription) { // 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取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; // 用_objc_msgForward函数指针代替imp,_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
    
        if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
            // 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下。
            IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
            if (originalForwardImp) {
                class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
            }
        }
    
        [cls jp_fixMethodSignature]; // fix bug
        if (class_respondsToSelector(cls, selector)) { // 类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
            NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName]; // ORIGFunc
            SEL originalSelector = NSSelectorFromString(originalSelectorName);
            if(!class_respondsToSelector(cls, originalSelector)) {
                class_addMethod(cls, originalSelector, originalImp, typeDescription);
            }
        }
        
        NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; // _JPFunc
        
        _initJPOverideMethods(cls); // 初始化_JSOverideMethods
        _JSOverideMethods[cls][JPSelectorName] = function;
        // 将方法名添加_JP前缀作为key,将js代码作为value存入_JSOverideMethods中
        // 存储JS代码: _JSOverideMethods字段: {类 = {"_JPFunc" = "function实现"}}
        
        
        // Replace the original selector at last, preventing threading issus when
        // the selector get called during the execution of `overrideMethod`
        class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); // 将方法替换为_objc_msgForward,调用直接跳转拦截调用
    

    方法逻辑:

    • 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取typeDescription
    • 获取方法是原来实现originalImp,以及设置拦截调用IMP:_objc_msgForward
    • 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下
    • 如果类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
    • 保存一份方法:将方法名新增_JP前缀,添加到全局对象_JSOverideMethods字典中:{类 = {"_JPFunc" = "function实现"}}
    • 执行方法替换,将当前方法selector的实现替换成msgForwardIMP,也就是肯定会跳转拦截调用,在拦截调用中捕获参数信息。

    执行到这里,我们看到JS里面保存了一份方法的实现,OC里面也存了一份。
    JS中:_ocCls
    OC中:_JSOverideMethods


    再次触发崩溃按钮,跟踪一下代码

    - (IBAction)btnClick:(id)sender 
    

    根据上面的解析,应该会走拦截调用方法,而我们已经将拦截调用的方法实现IMP替换成JPForwardInvocation()方法。接下来看一下这个方法。

    拦截调用方法JPForwardInvocation()

    static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
    {
        BOOL deallocFlag = NO;
        id slf = assignSlf;
        NSMethodSignature *methodSignature = [invocation methodSignature]; // 获取参数,返回值相关信息
        NSInteger numberOfArguments = [methodSignature numberOfArguments]; // 参数个数
        
        NSString *selectorName = NSStringFromSelector(invocation.selector); // 获取方法名
        NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; // _JPFunc
        JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName); // 获取之前存在 _JSOverideMethods字典中对应JS方法的实现
        if (!jsFunc) { // 如果没有这个方法实现,那么跳转系统自己的拦截调用方法,或者是工程中有实现的拦截调用方法,但是问题是如果有方法实现,那么工程中的拦截调用如果重写了,那么就不会执行ForwardInvocation方法了!!!!
            JPExecuteORIGForwardInvocation(slf, selector, invocation);
            return;
        }
        
        NSMutableArray *argList = [[NSMutableArray alloc] init]; // 存类型数组
        if ([slf class] == slf) { // slf 是 类 类型
            [argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
        } else if ([selectorName isEqualToString:@"dealloc"]) { // selectorName是dealloc方法
            [argList addObject:[JPBoxing boxAssignObj:slf]];
            deallocFlag = YES;
        } else { // 正常的对象类型
            [argList addObject:[JPBoxing boxWeakObj:slf]];
        }
        
        // 对参数进行解析,从i=2开始,就是对参数解析。  methodSignature: 返回值, 各个参数
        for (NSUInteger i = 2; i < numberOfArguments; i++) {
            const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
            switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
            
                ...
                
                case '@': {  // 对象类型
                    __unsafe_unretained id arg;
                    [invocation getArgument:&arg atIndex:i]; // 获取当前对象,例子中是UIButton类型 <UIButton: 0x7faa08704240; frame = (59 220; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x6080000385e0>>
                    if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
                        [argList addObject:(arg ? [arg copy]: _nilObj)]; // NSBlock 对象需要执行copy,猜测是防止释放吧
                    } else {
                        [argList addObject:(arg ? arg: _nilObj)]; // 添加到argList
                    }
                    break;
                }
               
                ...
                
                default: {
                    NSLog(@"error type %s", argumentType);
                    break;
                }
            }
        }
        
        if (_currInvokeSuperClsName[selectorName]) { // 处理父类方法
            Class cls = NSClassFromString(_currInvokeSuperClsName[selectorName]);
            NSString *tmpSelectorName = [[selectorName stringByReplacingOccurrencesOfString:@"_JPSUPER_" withString:@"_JP"] stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"_JP"];
            if (!_JSOverideMethods[cls][tmpSelectorName]) {
                NSString *ORIGSelectorName = [selectorName stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"ORIG"];
                [argList removeObjectAtIndex:0];
                id retObj = callSelector(_currInvokeSuperClsName[selectorName], ORIGSelectorName, [JSValue valueWithObject:argList inContext:_context], [JSValue valueWithObject:@{@"__obj": slf, @"__realClsName": @""} inContext:_context], NO);
                id __autoreleasing ret = formatJSToOC([JSValue valueWithObject:retObj inContext:_context]);
                [invocation setReturnValue:&ret];
                return;
            }
        }
        
        NSArray *params = _formatOCToJSList(argList); // 转换成JS类型的对象,格式:[{"__clsName": 类名, "__obj": 对象}, ...]
        char returnType[255];
        strcpy(returnType, [methodSignature methodReturnType]); // 获取返回值类型 此处v:void
        
        // Restore the return type
        if (strcmp(returnType, @encode(JPDouble)) == 0) { // 根据JS类型转换double
            strcpy(returnType, @encode(double));
        }
        if (strcmp(returnType, @encode(JPFloat)) == 0) { // 根据JS类型转换float
            strcpy(returnType, @encode(float));
        }
    
        // 处理返回值类型
        switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
           
            ... // 此处省略占篇幅的宏定义
    
            case 'v': {
    //            JP_FWD_RET_CALL_JS // 由于用宏不好调试,替换成下面代码
                
                JSValue *jsval;
                [_JSMethodForwardCallLock lock]; // 加锁
                jsval = [jsFunc callWithArguments:params]; // 执行js方法
                [_JSMethodForwardCallLock unlock]; // 解锁
                while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) {
                    NSArray *args = nil;
                    JSValue *cb = jsval[@"cb"];
                    if ([jsval hasProperty:@"sel"]) {
                        id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO);
                        args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]];
                    }
                    [_JSMethodForwardCallLock lock];
                    jsval = [cb callWithArguments:args];
                    [_JSMethodForwardCallLock unlock];
                }
                
                break;
            }
                
            case '{': {
                NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);
                #define JP_FWD_RET_STRUCT(_type, _funcSuffix) \
                if ([typeString rangeOfString:@#_type].location != NSNotFound) {    \
                    JP_FWD_RET_CALL_JS \
                    _type ret = [jsval _funcSuffix]; \
                    [invocation setReturnValue:&ret];\
                    break;  \
                }
                JP_FWD_RET_STRUCT(CGRect, toRect)
                JP_FWD_RET_STRUCT(CGPoint, toPoint)
                JP_FWD_RET_STRUCT(CGSize, toSize)
                JP_FWD_RET_STRUCT(NSRange, toRange)
                
                @synchronized (_context) {
                    NSDictionary *structDefine = _registeredStruct[typeString];
                    if (structDefine) {
                        size_t size = sizeOfStructTypes(structDefine[@"types"]);
                        JP_FWD_RET_CALL_JS
                        void *ret = malloc(size);
                        NSDictionary *dict = formatJSToOC(jsval);
                        getStructDataWithDict(ret, dict, structDefine);
                        [invocation setReturnValue:ret];
                        free(ret);
                    }
                }
                break;
            }
            default: {
                break;
            }
        }
        
        if (_pointersToRelease) {
            for (NSValue *val in _pointersToRelease) {
                void *pointer = NULL;
                [val getValue:&pointer];
                CFRelease(pointer);
            }
            _pointersToRelease = nil;
        }
        
        if (deallocFlag) {
            slf = nil;
            Class instClass = object_getClass(assignSlf);
            Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
            void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
            originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
        }
    }
    
    

    上面代码中有些...是省略了部分代码

    方法逻辑:

    • 获取当前替换方法调用方法的参数,拦截调用的主要作用在于此。否则直接使用Method Swizzle即可,何必再走拦截调用呢。

    • 通过在方法中添加_JP前缀,获取之前存在_JSOverideMethods中对应的JS方法实现,如果为空直接调用原来的拦截调用方法。

    • 将类,对象,各个参数转换为OC对象存入argList数组中

    • 处理父类方法

    • 处理返回值

    • 根据返回值类型,调用JS代码[jsFunc callWithArguments:params],也就是此处会调用之前封装过的JS代码,执行JS中的btnClick:方法,这个地方比较绕。封装过的JS代码不是很理解。JS功底比较好的同学可以指教一下。

    执行我们main.js里面btnClick:方法

    btnClick: function(sender) {
    var arrTest = ["arrTest"];
    var strCrash = arrTest[0];
    
    var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
        alert.show();
    }
    

    其实已经替换成了

    {
    var arrTest = ["arrTest"];
    var strCrash = arrTest[0];
    
    var alert = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")("JSPatchAmend", "Success", null, "Yes", null, null);
        alert.__c("show")();
    }
    

    我们把crash代码替换掉,并执行了一个原生的Alert,这里统一的方法调用都是调用__c()

    __C()方法的调用

    疑问:JS如何去调用__c()方法呢?

      for (var method in _customMethods) { // __c
        if (_customMethods.hasOwnProperty(method)) {
          // Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
          // Object.prototype 属性表示 Object 的原型对象。
          Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
        }
      }
    

    上面for循环我们可以看到,这里将_customMethods字典里面的key都会设置成这个对象的属性,由于这段代码没有调试出来,所以暂时理解为所有对象都添加__c、super、performSelectorInOC、performSelector方法,而__c方法实现是一个匿名函数, 返回值又是一个匿名函数

    下面为_customMethods字典的内容:

      var _customMethods = { // 字典对象,存 __c: function
        __c: function(methodName) {
          var slf = this
    
          if (slf instanceof Boolean) {
            return function() {
              return false
            }
          }
          if (slf[methodName]) {
            return slf[methodName].bind(slf);
          }
    
          if (!slf.__obj && !slf.__clsName) { // 未定义抛出异常
            throw new Error(slf + '.' + methodName + ' is undefined')
          }
          if (slf.__isSuper && slf.__clsName) { // 如果是调用父类方法
              slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName); // 通过调用oc的[cls superclass] 方法获取父类的名字
          }
          var clsName = slf.__clsName
          if (clsName && _ocCls[clsName]) { // 有缓存,返回缓存的方法
            var methodType = slf.__obj ? 'instMethods': 'clsMethods'  // 通过__obj判断是实例方法还是类方法
            if (_ocCls[clsName][methodType][methodName]) {
              slf.__isSuper = 0;
              return _ocCls[clsName][methodType][methodName].bind(slf) // // 返回的是之前缓存的方法
            }
          }
    
          return function(){
            var args = Array.prototype.slice.call(arguments) // 转换成数组, arguments 怎么获取的??
            return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper) // 返回方法调用的结果
          }
        },
    
        super: function() {
          var slf = this
          if (slf.__obj) {
            slf.__obj.__realClsName = slf.__realClsName;
          }
          return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
        },
    
        performSelectorInOC: function() {
          var slf = this
          var args = Array.prototype.slice.call(arguments)
          return {__isPerformInOC:1, obj:slf.__obj, clsName:slf.__clsName, sel: args[0], args: args[1], cb: args[2]}
        },
    
        performSelector: function() {
          var slf = this
          var args = Array.prototype.slice.call(arguments)
          return _methodFunc(slf.__obj, slf.__clsName, args[0], args.splice(1), slf.__isSuper, true)
        }
      }
    

    注意:
    方法调用例如:UIAlertView.__c("fucName")("param")
    第一个调用为方法名:("fucName"),参数为fucName,执行的是以__c 为key的匿名函数,此时获取的返回值是另一个匿名函数,再执行("param")则是调用的返回值的匿名函数,参数为param。

    解析一下__c()匿名函数的逻辑:

    • 做了异常处理

    • 处理父类方法

    • 如果这个JS方法在缓存_ocCls字典中,那么直接返回

    • 如果没有缓存,那么调用_methodFunc方法

    下面看下_methodFunc方法

    _methodFunc()方法

      var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
        var selectorName = methodName
        if (!isPerformSelector) { // 如果不是performselector,用正则对selectorName进行字符替换:__ 替换 -,- 替换 _,有':', 在最后添加上':'
          methodName = methodName.replace(/__/g, "-") // __ 替换 -
          selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_") // - 替换 _
          var marchArr = selectorName.match(/:/g) // 有':', 在最后添加上':'
          var numOfArgs = marchArr ? marchArr.length : 0
          if (args.length > numOfArgs) {
            selectorName += ":"
          }
        }
        var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
                             _OC_callC(clsName, selectorName, args) // 调用oc的生成方法:实例方法还是类方法
        return _formatOCToJS(ret)
      }
    

    作用:通过调用OC方法,实现方法调用,并返回调用结果

    方法逻辑:

    • 首先执行方法替换:
      __ 替换 -,- 替换 _,有':', 在最后添加上':'
    • 根据是否是实例对象来调用OC的方法_OC_callI(实例方法),_OC_callC(类方法)

    • 获取返回值,并格式转换为JS类型

    下面看下OC的方法调用_OC_callI,_OC_callC

    OC调用方法执行_OC_callI/_OC_callC

        context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { // call instace method
            return callSelector(nil, selectorName, arguments, obj, isSuper); // 返回值类型: {"__clsName": 类名; "__obj": 对象}
        };
        context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
            return callSelector(className, selectorName, arguments, nil, NO);
        };
        
    

    可以看到,这两个方法都是调用的callSelector()方法。

    下面看一下callSelector方法的实现:

    static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
    {
        NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];
       
        if (instance) {
            instance = formatJSToOC(instance);
            if (class_isMetaClass(object_getClass(instance))) { // 是元类
                className = NSStringFromClass((Class)instance);
                instance = nil;
            } else if (!instance || instance == _nilObj || [instance isKindOfClass:[JPBoxing class]]) {
                return @{@"__isNil": @(YES)};
            }
        }
        id argumentsObj = formatJSToOC(arguments); // 转换成oc对象数组
        
        if (instance && [selectorName isEqualToString:@"toJS"]) {
            if ([instance isKindOfClass:[NSString class]] || [instance isKindOfClass:[NSDictionary class]] || [instance isKindOfClass:[NSArray class]] || [instance isKindOfClass:[NSDate class]]) {
                return _unboxOCObjectToJS(instance);
            }
        }
    
        Class cls = instance ? [instance class] : NSClassFromString(className); // 传的是类或者对象
        SEL selector = NSSelectorFromString(selectorName);
        
        NSString *superClassName = nil;
        if (isSuper) { // 是否是父类的方法
            NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName];
            SEL superSelector = NSSelectorFromString(superSelectorName);
            
            Class superCls;
            if (realClsName.length) {
                Class defineClass = NSClassFromString(realClsName);
                superCls = defineClass ? [defineClass superclass] : [cls superclass];
            } else {
                superCls = [cls superclass];
            }
            
            Method superMethod = class_getInstanceMethod(superCls, selector);
            IMP superIMP = method_getImplementation(superMethod);
            
            class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod));
            
            NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
            JSValue *overideFunction = _JSOverideMethods[superCls][JPSelectorName];
            if (overideFunction) {
                overrideMethod(cls, superSelectorName, overideFunction, NO, NULL);
            }
            
            selector = superSelector;
            superClassName = NSStringFromClass(superCls);
        }
        
        
        NSMutableArray *_markArray;
        
        NSInvocation *invocation;
        NSMethodSignature *methodSignature;
        if (!_JSMethodSignatureCache) {
            _JSMethodSignatureCache = [[NSMutableDictionary alloc]init];
        }
        if (instance) { // 实例方法
            [_JSMethodSignatureLock lock];
            if (!_JSMethodSignatureCache[cls]) {
                _JSMethodSignatureCache[(id<NSCopying>)cls] = [[NSMutableDictionary alloc]init]; // 初始化key
            }
            methodSignature = _JSMethodSignatureCache[cls][selectorName]; // 新增缓存机制,提升效率
            if (!methodSignature) {
                methodSignature = [cls instanceMethodSignatureForSelector:selector]; //
                methodSignature = fixSignature(methodSignature);
                _JSMethodSignatureCache[cls][selectorName] = methodSignature;
            }
            [_JSMethodSignatureLock unlock];
            if (!methodSignature) {
                _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for instance %@", selectorName, instance]);
                return nil;
            }
            invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
            [invocation setTarget:instance];
        } else { // 类方法
            methodSignature = [cls methodSignatureForSelector:selector]; //
            methodSignature = fixSignature(methodSignature); // fix bug
            if (!methodSignature) {
                _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for class %@", selectorName, className]);
                return nil;
            }
            invocation= [NSInvocation invocationWithMethodSignature:methodSignature];
            [invocation setTarget:cls];
        }
        [invocation setSelector:selector];
        
        NSUInteger numberOfArguments = methodSignature.numberOfArguments;
        NSInteger inputArguments = [(NSArray *)argumentsObj count];
        if (inputArguments > numberOfArguments - 2) {
            // calling variable argument method, only support parameter type `id` and return type `id`
            id sender = instance != nil ? instance : cls;
            id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector); // 调用objc_msgSend方法
            return formatOCToJS(result);
        }
        
        for (NSUInteger i = 2; i < numberOfArguments; i++) { // 参数的处理,注意从第二个argument开始:0是方法名,1是':'
            const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
            id valObj = argumentsObj[i-2];
            switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
                    
                    ...
                    
                case '#': {
                    if ([valObj isKindOfClass:[JPBoxing class]]) {
                        Class value = [((JPBoxing *)valObj) unboxClass];
                        [invocation setArgument:&value atIndex:i];
                        break;
                    }
                }
                default: {
                    if (valObj == _nullObj) {
                        valObj = [NSNull null];
                        [invocation setArgument:&valObj atIndex:i];
                        break;
                    }
                    if (valObj == _nilObj ||
                        ([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) {
                        valObj = nil;
                        [invocation setArgument:&valObj atIndex:i];
                        break;
                    }
                    if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {
                        JSValue *blkJSVal = arguments[i-2];
                        Class JPBlockClass = NSClassFromString(@"JPBlock");
                        if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) {
                            __autoreleasing id cb = [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[blkJSVal[@"blockObj"] toObject]];
                            [invocation setArgument:&cb atIndex:i];
                        } else {
                            __autoreleasing id cb = genCallbackBlock(arguments[i-2]);
                            [invocation setArgument:&cb atIndex:i];
                        }
                    } else {
                        [invocation setArgument:&valObj atIndex:i];
                    }
                }
            }
        }
        
        if (superClassName) _currInvokeSuperClsName[selectorName] = superClassName;
        [invocation invoke]; // 方法调用
        if (superClassName) [_currInvokeSuperClsName removeObjectForKey:selectorName]; // 方法调用完又移除了缓存的父类方法名
        if ([_markArray count] > 0) {
            for (JPBoxing *box in _markArray) {
                void *pointer = [box unboxPointer];
                id obj = *((__unsafe_unretained id *)pointer);
                if (obj) {
                    @synchronized(_TMPMemoryPool) {
                        [_TMPMemoryPool setObject:obj forKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]];
                    }
                }
            }
        }
        
        char returnType[255];
        strcpy(returnType, [methodSignature methodReturnType]);
        
        // Restore the return type
        if (strcmp(returnType, @encode(JPDouble)) == 0) { // 如果是JPDouble结构体,返回double的编码d
            strcpy(returnType, @encode(double));
        }
        if (strcmp(returnType, @encode(JPFloat)) == 0) {
            strcpy(returnType, @encode(float));
        }
    
        id returnValue;
        if (strncmp(returnType, "v", 1) != 0) { // 不是void类型
            if (strncmp(returnType, "@", 1) == 0) { // id 类型
                void *result;
                [invocation getReturnValue:&result]; // 获取返回值result
                
                //For performance, ignore the other methods prefix with alloc/new/copy/mutableCopy
                if ([selectorName isEqualToString:@"alloc"] || [selectorName isEqualToString:@"new"] ||
                    [selectorName isEqualToString:@"copy"] || [selectorName isEqualToString:@"mutableCopy"]) {
                    returnValue = (__bridge_transfer id)result; // 这几个参数alloc/new/copy/mutableCopy会生成新对象,并且引用计数是1,需要我们持有,否则会提前释放?释放时机在哪?
                } else { // 一般类型的返回值:id
                    returnValue = (__bridge id)result;
                }
                return formatOCToJS(returnValue);
                
            } else {
                switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
                        
                    ...
                    
                    case '#': {
                        Class result;
                        [invocation getReturnValue:&result];
                        returnValue = formatOCToJS([JPBoxing boxClass:result]);
                        break;
                    }
                }
                return returnValue;
            }
        }
        return nil;
    }
    

    由于比较长,我们删掉了部分代码。

    方法逻辑:

    • 这里根据调用对象的类型来进行判断是否调用实例方法,还是类方法。并对参数进行转换成OC数组

    • 处理父类方法

    • 使用NSInvocation来执行方法调用,注意,类方法使用[cls methodSignatureForSelector:selector],而实例方法使用 [cls instanceMethodSignatureForSelector:selector]来初始化methodSignature。这里使用_JSMethodSignatureCache缓存methodSignature内容,提升方法调用效率

    • 处理返回值,这里逻辑挺多的,核心就是调用Runtime的objc_msgSend方法,对于参数的转换,这里不做讨论。


    至此,大体逻辑我们已经理清楚了。里面还有很多细节都写的挺好,还是值得去研究一下。


    如果文中有什么错误,欢迎大家指正。

    转载请注明出处:http://semyonxu.com

    代码注释Demo见:
    https://github.com/342261733/JSPatchAnalysis

    typeDescription类型的含义可以参考苹果文档:
    https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1

    参考JSPatch作者文档:
    https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3

    相关文章

      网友评论

      本文标题:JSPatch源码解析

      本文链接:https://www.haomeiwen.com/subject/wavckxtx.html