我在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.