iOS开发OC基础

iOS开发-JS与原生OC互相调用之JavaScriptCore

2018-10-09  本文已影响0人  iOS_ZZ

近期由于工作和个人的闲置没有及时的更新博客,为此对各位同学表示抱歉,那么废话不多说,今天我们就聊聊那些在iOS中JS与原生OC互相调用,那么废话不多说,直接上代码~

本文摘抄自:https://hjgitbook.gitbooks.io/ios/content/04-technical-research/04-javascriptcore-note.html

JavaScriptCore初探

JavaScriptCore介绍

使用Objective-C和JavaScript结合开发的好处?

JSContext / JSValue
JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext为JavaScript提供运行环境,通过
- (JSValue *)evaluateScript:(NSString *)script;
方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。 而JSContext的创建都是基于JSVirtualMachine:

- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。
创建一个 JSContext 后,可以很容易地运行 JavaScript 代码来创建变量,做计算,甚至定义方法:
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];
JSValue *tripleNum = [context evaluateScript:@"triple(num)"];
任何出自 JSContext 的值都被可以被包裹在一个 JSValue 对象中,JSValue 包装了每一个可能的 JavaScript 值:字符串和数字;数组、对象和方法;甚至错误和特殊的 JavaScript 值诸如 null 和 undefined。
可以对JSValue调用toString、toBool、toDouble、toArray等等方法把它转换成合适的Objective-C值或对象。

Objective-C调用JavaScript

例如有一个"Hello.js"文件内容如下:
function printHello() {

}
在Objective-C中调用printHello方法:
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"js"];
NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:scriptString];
JSValue *function = self.context[@"printHello"];
[function callWithArguments:@[]];
分析以上代码:
首先初始化了一个JSContext,并执行JavaScript脚本,此时printHello函数并没有被调用,只是被读取到了这个context中。
然后从context中取出对printHello函数的引用,并保存到一个JSValue中。
注意这里,从JSContext中取出一个JavaScript实体(值、函数、对象),和将一个实体保存到JSContext中,语法均与NSDictionary的取值存值类似,非常简单。
最后如果JSValue是一个JavaScript函数,可以用callWithArguments来调用,参数是一个数组,如果没有参数则传入空数组@[]。

JavaScript调用Objective-C

还是上面的例子,将"hello.js"的内容改为:
function printHello() {
    print("Hello, World!");
}
这里的print函数用Objective-C代码来实现
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"js"];
NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:scriptString];
self.context[@"print"] = ^(NSString *text) {
    NSLog(@"%@", text");
};
JSValue *function = self.context[@"printHello"];
[function callWithArguments:@[]];
这里将一个Block以"print"为名传递给JavaScript上下文,JavaScript中调用print函数就可以执行这个Objective-C Block。
注意这里JavaScript中的字符串可以无缝的桥接为NSString,实参"Hello, World!"被传递给了NSString类型的text形参。

JavaScript运行异常处理

当JavaScript运行时出现异常,会回调JSContext的exceptionHandler中设置的Block
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
      NSLog(@"JS Error: %@", exception);
};
[context evaluateScript:@"function multiply(value1, value2) { return value1 * value2 "];
// 此时会打印Log "JS Error: SyntaxError: Unexpected end of script"

JSExport协议介绍

看下面的例子:
@protocol ItemExport <JSExport>
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@end

@interface Item : NSObject <ItemExport>
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@end
注意Item类不去直接符合JSExport,而是符合一个自己的协议,这个协议去继承JSExport协议。
例如有如下JavaScript代码
function Item(name, description) {
    this.name = name;
    this.description = description;
}
var items = [];
function addItem(item) {
    items.push(item);
}
可以在Objective-C中把Item对象传递给addItem函数
Item *item = [[Item alloc] init];
item.name = @"itemName";
item.description = @"itemDescription";

JSValue *function = context[@"addItem"];
[function callWithArguments:@[item]];
或者把Item类导出到JavaScript环境,等待稍后使用
[self.context setObject:Item.self forKeyedSubscript:@"Item"];

内存管理陷阱

在block内捕获JSContext

self.context[@"getVersion"] = ^{
    NSString *versionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
    versionString = [@"version " stringByAppendingString:versionString];
    JSContext *context = [JSContext currentContext]; // 这里不要用self.context
    JSValue *version = [JSValue valueWithObject:versionString inContext:context];
    return version;
};
使用[JSContext currentContext]而不是self.context来在block中使用JSContext,来防止循环引用。

JSManagedValue的使用

#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h>
@interface MyAlertView : UIAlertView
- (id)initWithTitle:(NSString *)title
            message:(NSString *)message
            success:(JSValue *)successHandler
            failure:(JSValue *)failureHandler
            context:(JSContext *)context;
@end
按照一般自定义AlertView的实现方法,MyAlertView需要持有successHandler,failureHandler这两个JSValue对象向JavaScript环境注入一个function
self.context[@"presentNativeAlert"] = ^(NSString *title,
                                        NSString *message,
                                        JSValue *success,
                                        JSValue *failure) {
   JSContext *context = [JSContext currentContext];
   MyAlertView *alertView = [[MyAlertView alloc] initWithTitle:title 
                                                       message:message
                                                       success:success
                                                       failure:failure
                                                       context:context];
   [alertView show];
};
#import "MyAlertView.h"
@interface XorkAlertView() <UIAlertViewDelegate>
@property (strong, nonatomic) JSContext *ctxt;
@property (strong, nonatomic) JSMagagedValue *successHandler;
@property (strong, nonatomic) JSMagagedValue *failureHandler;
@end

@implementation MyAlertView
- (id)initWithTitle:(NSString *)title
            message:(NSString *)message
            success:(JSValue *)successHandler
            failure:(JSValue *)failureHandler
            context:(JSContext *)context {

    self = [super initWithTitle:title
                    message:message
                   delegate:self
          cancelButtonTitle:@"No"
          otherButtonTitles:@"Yes", nil];

    if (self) {
        _ctxt = context;
        _successHandler = [JSManagedValue managedValueWithValue:successHandler];
        // A JSManagedValue by itself is a weak reference. You convert it into a conditionally retained
        // reference, by inserting it to the JSVirtualMachine using addManagedReference:withOwner:
        [context.virtualMachine addManagedReference:_successHandler withOwner:self];

        _failureHandler = [JSManagedValue managedValueWithValue:failureHandler];
        [context.virtualMachine addManagedReference:_failureHandler withOwner:self];
    }
    return self;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == self.cancelButtonIndex) {
        JSValue *function = [self.failureHandler value];
        [function callWithArguments:@[]];
    } else {
        JSValue *function = [self.successHandler value];
        [function callWithArguments:@[]];
    }

    [self.ctxt.virtualMachine removeManagedReference:_failureHandler withOwner:self];
    [self.ctxt.virtualMachine removeManagedReference:_successHandler withOwner:self];
}    
@end
上一篇 下一篇

猜你喜欢

热点阅读