Files
knowledge-kit/Chapter1 - iOS/1.29.md
2026-02-22 22:12:36 +01:00

5.9 KiB

JavascriptCore

  1. JSCore is a JavaScript wrapper implemented in C/C++ based on WebKit that makes interaction between JavaScript and native code simpler.
  • JSContext
    JSContext represents an instance of a JavaScript execution environment. All JavaScript execution occurs inside a context. JSContext is also used to manage the lifecycle of JavaScript objects in the VM.

  • JSValue
    JSValue is used to receive the return results from JSContext execution. A JSValue can be any JavaScript type (primitive, object, function, etc.).

  • JSManagedValue
    JSManagedValue is a wrapper around JSValue that helps resolve retain-cycle issues between JS and Objective-C. The most common use is to safely reference a JSValue stored on the heap. If a JSValue is stored on the heap incorrectly, it can easily create retain cycles that prevent JSContext from being released properly.

  • JSExport
    A protocol used to expose native objects to JS; the exposed object can point to itself and to other objects.

  • JSVirtualMachine
    Manages the JS object space and needed resources.

  1. Native calling JS
  • Load JS code (JSValue *)evaluateScript:(NSString *)script;

  • Call JS function JSValue *callback = self.context[@"sayHi"]; [callback callWithArguments:@[@"Hangzhou Xiao Liu"]];

  1. JS calling Native
  • Implement via a block. Then call the method directly from JS. Note: inside the block do not directly use externally defined JSContext or JSValue; pass them as parameters or obtain them via + (JSContext *)currentContext; otherwise it may cause retain cycles and memory won't be freed correctly. self.context[@"showMessage"] = ^(NSString *message){ UIAlertController *alertCtr = [UIAlertController alertControllerWithTitle:@"Notice" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]; [alertCtr addAction:cancel]; // Note: the block executes on a background thread; update UI on main thread. dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf presentViewController:alertCtr animated:YES completion:nil]; }); };

  • Implement via the JSExport protocol; JS will call methods on an injected Objective-C object, so the methods must be declared in the protocol and implemented on the injected object. Inject the native object when the web view finishes loading. // Protocol declaration

    @protocol JSInject <JSExport>
    - (void)showMessage:(NSString *)message;
    @end
    
    // Implementation
    
    - (void)showMessage:(NSString *)message{
          UIAlertController *alertCtr = [UIAlertController alertControllerWithTitle:@"Notice" message:message preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
            [alertCtr addAction:cancel];
            // Note: the method runs on a background thread; update UI on main thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf presentViewController:alertCtr animated:YES completion:nil];
            });
    }
    
    // Injection
    
    - (void)webViewDidFinishLoad:(UIWebView *)webView
    {
        // Get the JSContext from the web view.
        self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
        // Inject the Objective-C object needed by JS
        self.context[@"Bridge"] = [JSInject new];
    }
    

Example

JS calls Native

// Native object to expose (with some properties and methods)
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol PersonInjectExport<JSExport>

@property (nonatomic, strong) NSString *name;

@property (nonatomic, strong) NSString *hobby;

- (id)sayHi;

@end


@interface PersonInject : NSObject<PersonInjectExport>

@property (nonatomic, strong) NSString *name;

@property (nonatomic, strong) NSString *hobby;

- (id)sayHi;

@end


// view controller

- (void)webViewDidFinishLoad:(UIWebView *)webView{
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    PersonInject *person = [[PersonInject alloc] init];
    person.name = @"Hangzhou Xiao Liu";
    person.hobby = @"Coding, Movie, Music, Table tennis, Fit";
    self.jsContext[@"lbp"] = person;
}

// JS
<body>

  Hi. Hello everyone, I am
  <p id="name">***</p>

  <button id="show">Then do an introduction</button>

</body>
<script>
  var u = navigator.userAgent
  var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1
  var isiOS = u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // iOS device  

  document.getElementById("show").onclick = function () {
    if (isiOS) {
      document.getElementById("name").innerHTML = lbp.name;
      setTimeout(() => {
        alert(lbp.sayHi());
      }, 1000);
    }
  }
</script>

Native calls JS

// Native
- (void)callJS{
    JSValue *functionName = self.jsContext[@"sum"];
    NSInteger sum = [[functionName callWithArguments:@[@"2",@"18"]] toInt32];
    
    UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"Calculation from JS" message:[NSString stringWithFormat:@"%zd",sum] preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
    [alertVC addAction:okAction];
    [self presentViewController:alertVC animated:YES completion:nil];
}

// JS
function sum(a ,b){
	return parseInt(a) + parseInt(b);
}