我在iOS应用程序中使用
JavaScriptCore库,我正在尝试实现setTimeout函数.
setTimeout(func,period)
启动应用程序后,将创建具有全局上下文的JSC引擎,并向该上下文添加两个函数:
_jscontext = JSGlobalContextCreate(NULL); [self mapName:"iosSetTimeout" toFunction:_setTimeout]; [self mapName:"ioslog" toFunction:_log];
这是本机实现,它将具有所需名称的全局JS函数映射到静态目标C函数:
- (void) mapName:(const char*)name toFunction:(JSObjectCallAsFunctionCallback)func
{
JsstringRef nameRef = JsstringCreateWithUTF8CString(name);
JSObjectRef funcRef = JSObjectMakeFunctionWithCallback(_jscontext,nameRef,func);
JSObjectSetProperty(_jscontext,jscontextGetGlobalObject(_jscontext),funcRef,kJSPropertyAttributeNone,NULL);
Jsstringrelease(nameRef);
}
这里是目标C setTimeout函数的实现:
JSValueRef _setTimeout(jscontextRef ctx,JSObjectRef function,JSObjectRef thisObject,size_t argumentCount,const JSValueRef arguments[],JSValueRef* exception)
{
if(argumentCount == 2)
{
JSEngine *jsEngine = [JSEngine shared];
jsEngine.timeoutCtx = ctx;
jsEngine.timeoutFunc = (JSObjectRef)arguments[0];
[jsEngine performSelector:@selector(onTimeout) withObject:nil afterDelay:5];
}
return JSValueMakeNull(ctx);
}
一段延迟后应该在jsEngine上调用的函数:
- (void) onTimeout
{
JSValueRef excp = NULL;
JSObjectCallAsFunction(timeoutCtx,timeoutFunc,NULL,&excp);
if (excp) {
JsstringRef exceptionArg = JSValuetoStringcopy([self jscontext],excp,NULL);
Nsstring* exceptionRes = (__bridge_transfer Nsstring*)JsstringcopyCFString(kcfAllocatorDefault,exceptionArg);
Jsstringrelease(exceptionArg);
NSLog(@"[JSC] JavaScript exception: %@",exceptionRes);
}
}
用于javascript评估的本机函数:
- (Nsstring *)evaluate:(Nsstring *)script
{
if (!script) {
NSLog(@"[JSC] JS String is empty!");
return nil;
}
JsstringRef scriptJS = JsstringCreateWithUTF8CString([script UTF8String]);
JSValueRef exception = NULL;
JSValueRef result = JSEvaluateScript([self jscontext],scriptJS,&exception);
Nsstring *res = nil;
if (!result) {
if (exception) {
JsstringRef exceptionArg = JSValuetoStringcopy([self jscontext],exception,NULL);
Nsstring* exceptionRes = (__bridge_transfer Nsstring*)JsstringcopyCFString(kcfAllocatorDefault,exceptionArg);
Jsstringrelease(exceptionArg);
NSLog(@"[JSC] JavaScript exception: %@",exceptionRes);
}
NSLog(@"[JSC] No result returned");
} else {
JsstringRef jstrArg = JSValuetoStringcopy([self jscontext],result,NULL);
res = (__bridge_transfer Nsstring*)JsstringcopyCFString(kcfAllocatorDefault,jstrArg);
Jsstringrelease(jstrArg);
}
Jsstringrelease(scriptJS);
return res;
}
在整个设置之后,JSC引擎应该评估这个:
[jsEngine evaluate:@"iosSetTimeout(function(){ioslog('timeout done')},5000)"];
JS执行调用本机_setTimeout,五秒后,调用本机onTimeout并在JSObjectCallAsFunction中发生崩溃. timeoutCtx变为无效.听起来像超时功能上下文是本地的,在此期间垃圾收集器删除JSC端的上下文.
有趣的是,如果更改_setTimeout函数以便立即调用JSObjectCllAsFunction,而不等待超时,那么它将按预期工作.
如何防止这种异步回调中的自动上下文删除?
解决方法
我最终将setTimeout添加到这样的特定JavaScriptCore上下文中,并且它运行良好:
JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
jscontext *context = [[jscontext alloc] initWithVirtualMachine: vm];
// Add setTimout
context[@"setTimeout"] = ^(JSValue* function,JSValue* timeout) {
dispatch_after(dispatch_time(disPATCH_TIME_Now,(int64_t)([timeout toInt32] * NSEC_PER_MSEC)),dispatch_get_main_queue(),^{
[function callWithArguments:@[]];
});
};
在我的例子中,这允许我在JavaScriptCore中使用cljs.core.async / timeout.