美文网首页
JSPatch源码解析

JSPatch源码解析

作者: 不拘小节123456 | 来源:发表于2021-10-13 17:35 被阅读0次

    原理简单说就是:1,解析js文件(通过JSContext).2,通过runtimeApi动态创建类.3,可以hook原生Api进行拦截(该hook方案不存在继承关系间hook可能失败问题)

    1,简单的js调原生

    js业务代码
    defineClass('JPViewController', {
      handleBtn: function(sender) {
        console.log('------------handleBtn -- JPTableViewController');
        var tableViewCtrl = JPTableViewController.alloc().init()
        self.navigationController().pushViewController_animated(tableViewCtrl, YES)
      }
    })
    经过正则过滤之后
    defineClass('JPViewController', {
      handleBtn: function(sender) {
        console.__c("log")('------------handleBtn -- JPTableViewController');
        var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()
        self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
      }
    })
    
    在JSPatch.js解析js文件中代码
    
      var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
          
        var selectorName = methodName
        if (!isPerformSelector) {
          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)
        return _formatOCToJS(ret)
      }
    
      var _customMethods = {
        __c: function(methodName) {
          var slf = this
            console.log('------------customMethods')
            console.log(slf.__obj)
            console.log(slf.__clsName)
            console.log(methodName)
          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);
          }
          var clsName = slf.__clsName
          if (clsName && _ocCls[clsName]) {
            var methodType = slf.__obj ? 'instMethods': 'clsMethods'
            if (_ocCls[clsName][methodType][methodName]) {
              slf.__isSuper = 0;
              return _ocCls[clsName][methodType][methodName].bind(slf)
            }
          }
    
          return function(){
            var args = Array.prototype.slice.call(arguments)
            return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
          }
        },
    
        super: function() {
          var slf = this
          if (slf.__obj) {
            slf.__obj.__realClsName = slf.__realClsName;
          }
            console.log('-----------super')
            console.log(slf.__clsName)
          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)
        }
      }
    
     for (var method in _customMethods) {
        if (_customMethods.hasOwnProperty(method)) {
          Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
        }
      }
    
    //原生部分代码
        context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
            return callSelector(nil, selectorName, arguments, obj, isSuper);
        };
        context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
            return callSelector(className, selectorName, arguments, nil, NO);
        };
    
    static id formatJSToOC(JSValue *jsval)
    {
        id obj = [jsval toObject];
        if (!obj || [obj isKindOfClass:[NSNull class]]) return _nilObj;
        
        if ([obj isKindOfClass:[JPBoxing class]]) return [obj unbox];
        if ([obj isKindOfClass:[NSArray class]]) {
            NSMutableArray *newArr = [[NSMutableArray alloc] init];
            for (int i = 0; i < [(NSArray*)obj count]; i ++) {
                [newArr addObject:formatJSToOC(jsval[i])];
            }
            return newArr;
        }
        if ([obj isKindOfClass:[NSDictionary class]]) {
            if (obj[@"__obj"]) {
                id ocObj = [obj objectForKey:@"__obj"];
                if ([ocObj isKindOfClass:[JPBoxing class]]) return [ocObj unbox];
                return ocObj;
            } else if (obj[@"__clsName"]) {
                return NSClassFromString(obj[@"__clsName"]);
            }
            if (obj[@"__isBlock"]) {
                Class JPBlockClass = NSClassFromString(@"JPBlock");
                if (JPBlockClass && ![jsval[@"blockObj"] isUndefined]) {
                    return [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[jsval[@"blockObj"] toObject]];
                } else {
                    return genCallbackBlock(jsval);
                }
            }
            NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init];
            for (NSString *key in [obj allKeys]) {
                [newDict setObject:formatJSToOC(jsval[key]) forKey:key];
            }
            return newDict;
        }
        return obj;
    }
    总结:业务js代码经过正则后编译成js解析器可以解析到js代码,通过JSPatch.js文件根据原生注册的方法进行交互
    

    2,部分runtimeApi

    //创建对象,并进行注册
    cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
    objc_registerClassPair(cls);
    //创建协议,并进行添加
    Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
     class_addProtocol (cls, protocol);
    //添加方法
    class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
    //添加属性:通过关联对象技术来添加属性
    class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
    class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
    static id getPropIMP(id slf, SEL selector, NSString *propName) {
        return objc_getAssociatedObject(slf, propKey(propName));
    }
    static void setPropIMP(id slf, SEL selector, id val, NSString *propName) {
        objc_setAssociatedObject(slf, propKey(propName), val, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    3,hook原有Api.
    JSPatch的hook之所以可以不存在继承链hook的问题,是因为给原有的Method指向了一个新的SEL缓存了,在消息转发拦截的时候在重新调用这个,他由于没有交换,所以不存在这个问题

    //获取forwardImp,可以直接进入到消息转发流程,进行统一拦截
        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
    //将原来的函数Imp指向自定义名字的SEL(拼接ORIG)
    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);
            }
        }
    //将原有SEL指向forwardImp进行统一拦截
    class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
    

    //常用hook方法,如果继承关系进行hook会有问题,如果有三层继承关系 Base -> Parenet -> Child ,Base有函数(-(void)text;),Parenet(-(void)text_parent;),Child(-(void)text_child),如果先hookChild的(text ->text_child),在hookparent(text->text_parent),会出现text_parent无法调用.

    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
    {
        // the method might not exist in the class, but in its superclass
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // 如果当前类没有改方法,添加该方法,大部分情况是子类没有改方法,父类有,
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        
        // the method doesn’t exist and we just added one
        if (didAddMethod) {
    //将swizzledSelector 指向原来的Method
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
    }
    

    demo:
    https://github.com/riceForChina/JSPatchDemo.git

    相关文章

      网友评论

          本文标题:JSPatch源码解析

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