iOS2018. 2. 13. 15:51


iOS7이전에는 자바스크립트와 무언가 작용을 하려면 UIWebView의 stringByEvaluatingJavaScriptFormString:를 사용해야 했다. 이는 웹뷰의 자바스크립트 런타임에서만 가능했다. 이는 싱글스레드인 환경의 제약이 있다.


JavaScriptCore는 Objective-C에서 자바스크립트의 풀런타임환경에 접근이 가능하게 한다. 문법확인, 스크립트실행과 변수 접근, 콜백수신  그리고 Objective-C 객체의 공유등으로 광범위한 상호작용이 가능하다.


JSVirtualMachine클래스로 불리는 가상머신에서 실행된다. 이는 여러개의 VM을 통해 멀티스레드 자바스크립팅이 가능하다는 것이다. JSVirtualMachine 각각은 여러개의 JSContext를 가질 수 있다. JSContext는 자바스크립트 런타입환경에 말하고 몇가지 기능을 제공한다. 이 중 두가지는 글로벌객체에 접근하는것과 스크립트를 실행할 수 있다는 것이다.


글로벌 영역에 정의된 변수에 대한 접근은 키로 바로 접근이 가능하다. 


    JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
    context[@"a"] = @5;
    JSValue *aValue = context[@"a"];
    double a = [aValue toDouble];
    NSLog(@"%.0f", a);

함수호출은 다음과 같다


[context evaluateScript:@”var square = function(x) {return x*x;}”]; 

JSValue *squareFunction = context[@”square”]; 

NSLog(@”%@”, squareFunction); 

JSValue *aSquared = [squareFunction callWithArguments:@[context[@”a”]]]; 

NSLog(@”a^2: %@”, aSquared); 

JSValue *nineSquared = [squareFunction callWithArguments:@[@9]]; 

NSLog(@”9^2: %@”, nineSquared);


evaluateScript로 스크립트 코드를 로드하고 callWithArguments로 스크립트내의 함수를 호출한다. 호출된 결과는 JSValue로 반환된다.

스크립트코드는 블럭코드형태로도 가능하다.



    context[@"factorial"] = ^(int x) {
        int factorial = 1;
        for (; x > 1; x--) {
            factorial *= x;
        }
        return factorial;
    };
    [context evaluateScript:@"var fiveFactorial = factorial(5);"];
    JSValue *fiveFactorial = context[@"fiveFactorial"];
    NSLog(@"5! = %@", fiveFactorial);
스크립트와 objective c간의 타입관계는 다음과 같다.

//   Objective-C type  |   JavaScript type
// --------------------+---------------------
//         nil         |     undefined
//        NSNull       |        null
//       NSString      |       string
//       NSNumber      |   number, boolean
//     NSDictionary    |   Object object
//       NSArray       |    Array object
//        NSDate       |     Date object
//       NSBlock       |   Function object
//          id         |   Wrapper object
//        Class        | Constructor object
스크립트NSNumber와 NSBlocks 타입은 mutable이 아니다 즉 한쪽에서 변경했다고 다른쪽에서 반영이 되지 않는다. id와 Class는 mutable이다. 
임의 오브젝트나 클래스가 자바스크립트에 브릿지될 수 있다. 
NSNull, NSString, NSNumber, NSDictionary, NSArray 또는 NSDate는 관련 클래스 계층을 JavaScript 실행 컨텍스트로 가져 와서 동등한 클래스 및 프로토 타입을 생성하는 JavaScriptCore를 생성하려고 한다.

JSExport protocol로 자바스크립트에 커스텀클래스의 일부를 노출할 수 있다. 래퍼객체는 다른곳에 생성될것이다. 그리하여 한개의 객체가 양쪽의 실행컨텍스트에 공유될 수 있다.

다음이 그 예이다.

    @protocol ThingJSExports <JSExport>
    @property (nonatomic, copy) NSString *name;
    @end

    @interface Thing : NSObject <ThingJSExports>
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic) NSInteger number;
    @end

    @implementation Thing
    - (NSString *)description {
        return [NSString stringWithFormat:@"%@: %d", self.name, self.number];
    }
    @end

JSExport를 상속하여 ThingJSExports를 선언함으로써 자바스크립트에 노출할 수 있다. 이제 objective-c객체의 정보를 변경하면 자바스크립트에 그 값이 반영이 될것이다. 그 반대로 마찬가지.
다음은 id와 class를 브릿지한 예이다.


다음은 id와 class를 브릿지한 예이다.    Thing *thing = [[Thing alloc] init];
    thing.name = @"Alfred";
    thing.number = 3;
    context[@"thing"] = thing;
    JSValue *thingValue = context[@"thing"];

context[@"thing"] = [Thing class];

NSString *thingScript =

@"var t = new thing();"

"t.name = 'yslee';"

"t.number = 10;"

[context eveluateScript:thingScript];








Posted by 삼스