JavaScriptCore 에서 JSpatch, ReactNative 까지

12194 단어
· 애플 은 iOS 7 에서 OC 와 JS 의 상호작용 을 해결 하기 위해 자 바스 크 립 트 코 어 를 출시 했다.JavaScriptCore 는 이 환경 을 통 해 JS 코드 를 실행 할 수 있 는 JS 실행 환경 을 제공 합 니 다.JSpatch, ReactNative 는 모두 이 점 을 이용 하여 개발 한 것 이다.세 가 지 를 설명 하 러 왔 습 니 다.
  • JavaScriptCore
  • JSPatch
  • ReactNative

  • JavaScriptCore
    JS 는 OC 처럼 실행 되 는 과정 에서 자신의 역할 영역 이 있 습 니 다. 이 역할 영역 은 모든 역할 영역 내 변수 와 함 수 를 가 져 올 수 있 습 니 다. JavaScriptCore 는 JS 코드 를 실행 하 는 '역할 영역' 을 제공 합 니 다.
    JSContext,JSValue
    OC call JS
    JSContext *jsContext = [[JSContext alloc] init];
      //define var
      [jsContext evaluateScript:@"var jsCode = 2"];
      JSValue *jscode = jsContext[@"jsCode"];
      
      //define method
      [jsContext evaluateScript:@"function min(a,b) { if (a > b) {return b;} return a}"];
      
      //way 1
      [jsContext evaluateScript:@"min(5,4)"];
      //way 2
      JSValue *min = jsContext[@"min"];
      [min callWithArguments:@[@5,@4]];
    

    OC 는 '역할 영역' JScontext 를 만들어 서 독립 된 JS 실행 환경, 실행 방법 과 변수 정의 코드 를 구축 하고 아래 표 시 된 방식 으로 방법 과 변 수 를 가 져 올 수 있 으 며 호출 방법 에서 문자열 문 구 를 실행 하거나 JSValue 의 call 방법 을 선택 할 수 있 습 니 다.
    JS call OC
    JS 의 코드 는 항상 OC 가 만 든 JScontext 로 실행 되 어야 하기 때문에 OC 는 JS 코드 의 생김새 를 명확 하 게 호출 하면 직접 호출 할 수 있 습 니 다. 반대로 JS 는 OC 코드 의 구체 적 인 생김새 를 알 수 없고 알 아 도 명확 하 게 호출 할 수 없 기 때문에 애플 은 OC 의 block 을 JSValue 로 전환 하여 JScontext 에 등록 할 수 있 습 니 다.그리고 JS 코드 는 변경 block 을 가 져 오고 호출 할 수 있 습 니 다.
        JSContext *jsContext = [[JSContext alloc] init];
        jsContext[@"ocMin"]= ^(int a, int b) {
            if (a > b) {
                return b;
            }
            return a;
        };
        [jsContext evaluateScript:@"ocMin(5, 4)"];
    

    JSExport
    @protocol JSExport
    @end
    #define JSExportAs(PropertyName, Selector) \
        @optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector
    #endif
    
    #import 
    #import 
    @protocol JSExportProtocol 
    - (void)text;
    @end
    
    @interface JSExportText : NSObject 
    - (void)text;
    @end
    
    @implementation JSExportText
    - (void)text
    {
        NSLog(@"text");
    }
    @end
    
    //test code
        JSContext *jsContext = [[JSContext alloc] init];
        //        jsContext ,    JSExportText     
       jsContext[@"JSExportText"] = [JSExportText class];
       [jsContext evaluateScript:@"var jsExportText = JSExportText.getInstance(); jsExportText.text()"];
    

    JSexportProtocol 은 JSexport 를 따 릅 니 다. 그러면 JavaScriptCore 는 이러한 방법 과 속성 을 자동 으로 설정 하여 JS 호출 이 현재 의 방법 을 얻 을 수 있 도록 합 니 다.
    JSPatch
    jsPatch 는 현재 iOS 에서 가장 자주 사용 되 는 열 업데이트 방법 으로 순수 Swift 코드 는 지원 되 지 않 지만.
    프로 세 스
    1. JS 코드 (재 작성 방법 과 속성 을 정의 함) - > 2. 전 환 된 JS 코드 - > 3. 방법 교체 메모리 - > 4. 방법 호출 전송 찾기 이 네 단 계 는 JSpatch 의 전체 과정 입 니 다. 우 리 는 하나의 예 를 통 해 전체 과정 을 설명 합 니 다.
    defineClass("JPTableViewController", {
      viewDidLoad: function() {
         self.ORIGviewDidLoad();
      },
    })
    

    이상 은 공식 적 인 예 입 니 다. 목적 은 JPTable ViewController 의 view DidLoad 를 다시 쓰 는 것 입 니 다. 상기 코드 는 사실은 defineClass 함 수 를 호출 한 것 입 니 다. 첫 번 째 매개 변 수 는 클래스 이름, 두 번 째 배열 입 니 다. 인 스 턴 스 방법 배열 입 니 다. 세 번 째 매개 변 수 는 추가 되 지 않 았 습 니 다. 추가 하면 클래스 방법 배열 입 니 다. 이런 표기 법 은 매우 명확 합 니 다. 지난 절 에서 JS 는 context 가 실행 되 어야 한 다 는 것 을 알 고 있 습 니 다.그러나 위의 코드 에 있 는 defineClass 라 는 함 수 는 정의 되 지 않 았 습 니 다. 어떻게 실행 합 니까?사실 JSpatch 는 코드 를 실행 하기 전에 많은 작업 을 했 습 니 다. 다음은 JSpatch 의 초기 화 부분 코드 입 니 다. 중간 에 많이 생략 되 었 습 니 다.
    [JPEngine startEngine]; //     
    [JPEngine evaluateScript:string]; //  
    
    //       
    + (void)startEngine
    {
        if (_context) {
            return;
        }
        
        JSContext *context = [[JSContext alloc] init];
        
        context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
            return defineClass(classDeclaration, instanceMethods, classMethods);
        };
        
    ////      
        context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
            NSLog(@"%@", exception);
            NSAssert(NO, @"js exception: %@", exception);
        };
        
        _context = context;
        
        NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"WatchDog" ofType:@"js"];
        NSAssert(path, @"can't find JSPatch.js");
        NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
        [_context evaluateScript:jsCore];
    }
    
    

    위 에서 보 듯 이 JSpatch 는 context 를 가지 고 js 코드 를 실행 하지만 defineClass 의 정 의 를 보지 못 했 습 니 다. 우 리 는 상단 코드 의 마지막 부분 [ context evaluate Script: jsCore] 을 유의 합 니 다.이 코드 는 JSpatch. js 에서 실 행 된 코드 입 니 다. JSpatch. js 코드 를 캡 처 합 니 다.
    var global = this
     global.defineClass = function(declaration, instMethods, clsMethods) {
        var newInstMethods = {}, newClsMethods = {}
        _formatDefineMethods(instMethods, newInstMethods)
        _formatDefineMethods(clsMethods, newClsMethods)
    
        var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
    
        return require(ret["cls"])
      }
    

    ok, 양 끝 코드 결합 defineclass - >OC_defineClass - > defineClass () (oc 내부 코드 defineclass) 두 번 째 단 계 는 js 방법 을 내부 방법 으로 정의 하 는 방법 입 니 다.
    static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
    {
        NSString *className;
        NSString *superClassName;
        NSString *protocolNames;
        
        NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
        [scanner scanUpToString:@":" intoString:&className];
        if (!scanner.isAtEnd) {
            scanner.scanLocation = scanner.scanLocation + 1;
            [scanner scanUpToString:@"" intoString:&protocolNames];
            }
        }
        NSArray *protocols = [protocolNames componentsSeparatedByString:@","];
        if (!superClassName) superClassName = @"NSObject";
        className = trim(className);
        superClassName = trim(superClassName);
        
        Class cls = NSClassFromString(className);
        if (!cls) {
            Class superCls = NSClassFromString(superClassName);
            cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
            objc_registerClassPair(cls);
        }
        
    //        
        for (int i = 0; i < 2; i ++) {
            BOOL isInstance = i == 0;
            JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
            
            Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
            NSDictionary *methodDict = [jsMethods toDictionary];
            for (NSString *jsMethodName in methodDict.allKeys) {
                if ([jsMethodName isEqualToString:@"__c"]) {
                    continue;
                }
                JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
                int numberOfArg = [jsMethodArr[0] toInt32];
                NSString *tmpJSMethodName = [jsMethodName stringByReplacingOccurrencesOfString:@"__" withString:@"-"];
                NSString *selectorName = [tmpJSMethodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
                selectorName = [selectorName stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
                
                if (!countArgRegex) {
                    countArgRegex = [NSRegularExpression regularExpressionWithPattern:@":" options:NSRegularExpressionCaseInsensitive error:nil];
                }
                NSUInteger numberOfMatches = [countArgRegex numberOfMatchesInString:selectorName options:0 range:NSMakeRange(0, [selectorName length])];
                if (numberOfMatches < numberOfArg) {
                    selectorName = [selectorName stringByAppendingString:@":"];
                }
                
                JSValue *jsMethod = jsMethodArr[1];
                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);
                            overrided = YES;
                            break;
                        }
                    }
                    if (!overrided) {
                        NSMutableString *typeDescStr = [@"@@:" mutableCopy];
                        for (int i = 0; i < numberOfArg; i ++) {
                            [typeDescStr appendString:@"@"];
                        }
                        overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
                    }
                }
            }
        }
        
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
        class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
    #pragma clang diagnostic pop
    
        return @{@"cls": className};
    }
    
    
    \\      
    static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
    {
    //    
        SEL selector = NSSelectorFromString(selectorName);
        NSMethodSignature *methodSignature;
        IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;
    //               
        IMP msgForwardIMP = _objc_msgForward;
        class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
    
    //      
        if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
            IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
            class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
        }
    
    //       
        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);
            }
        }
      
        NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
        SEL JPSelector = NSSelectorFromString(JPSelectorName);
        NSString *clsName = NSStringFromClass(cls);
    //  js   
        if (!_JSOverideMethods[clsName][JPSelectorName]) {
            _initJPOverideMethods(clsName);
            _JSOverideMethods[clsName][JPSelectorName] = function;
            class_addMethod(cls, JPSelector, msgForwardIMP, typeDescription);
        }
    }
    

    위의 일부 코드 를 간소화 하 였 다.이 과정 은 다음 과 같다.objc_msg Forward, 이렇게 모든 호출 방법 이 호출 됩 니 다objc_msg Forward, 이 럴 때 forward Invocation 2 를 호출 합 니 다. forward Invocation 을 JP Forward Invocation 으로 바 꿉 니 다. 이 방법 은 마지막 으로 JP Forward Invocation 3 을 호출 합 니 다. 원래 의 방법 을 ORIG 로 시작 하 는 방법 으로 바 꾸 면 원래 의 방법 을 찾 을 수 있 습 니 다. 4. 실제 JS 의 코드 function 을 사전 에 저장 합 니 다. 사전 은...클래스 이름 은 key, 값 은 사전 입 니 다. 이 사전 은 방법 은 key, 값 은 js 의 function 형식, jSVValue 를 저장 합 니 다.
    전체 과정 정리 1. 초기 화 과정 definclass (개발 자가 직접 쓴 js 코드 호출 defineclass) - > definclass (세 개의 매개 변수, 유형, 방법, 클래스 방법 으로 전환) - >OC_defineClass (js function 을 전역 사전 에 저장 하고 원래 방법 을 저장 하 며 원래 방법 을 방법 으로 호출 하여 전송) 2. 실행 과정 callMethod (OC 의 방법) - > JP Forward Invocation (매개 변 수 를 초기 화하 고 js function 을 찾 습 니 다) - > JSfunction (실행)
    전체 과정 을 JSpatch 가 이 용 했 습 니 다.
  • OC 는 Context 에 미리 설정 되 어 있 습 니 다OC_define Class, 이렇게 실제 교체 과정 을 받 아 들 였 습 니 다
  • 그 다음 에 JSValue 의 call 방법 을 직접 사용 하여 JS 방법 을 집행 한다. 이런 것들 을 제외 하고 JSpatch 는 undefined 가 nil 로 변 하 는 등 전환 과정 도 많이 했다.그리고 OC 실례 찾기 등등
  • ReactNative
    계속

    좋은 웹페이지 즐겨찾기