我在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); }
- (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.