Implementação do iOS de “window.setTimeout” com o JavascriptCore

Estou usando a biblioteca JavaScriptCore dentro do aplicativo iOS e estou tentando implementar a função setTimeout.

setTimeout(func, period)

Depois que o aplicativo é iniciado, o mecanismo JSC com contexto global é criado e duas funções são incluídas nesse contexto:

_JSContext = JSGlobalContextCreate(NULL);

[self mapName:"iosSetTimeout" toFunction:_setTimeout];
[self mapName:"iosLog" toFunction:_log];

Aqui está a implementação nativa que está mapeando a função JS global com o nome desejado para a função C do objetivo estático:

- (void) mapName:(const char*)name toFunction:(JSObjectCallAsFunctionCallback)func
{
  JSStringRef nameRef = JSStringCreateWithUTF8CString(name);
  JSObjectRef funcRef = JSObjectMakeFunctionWithCallback(_JSContext, nameRef, func);
  JSObjectSetProperty(_JSContext, JSContextGetGlobalObject(_JSContext), nameRef, funcRef, kJSPropertyAttributeNone, NULL);
  JSStringRelease(nameRef);
}

E aqui está a implementação da função setTimeout do objetivo C:

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);
}

Função que deve ser chamada no jsEngine após algum atraso:

- (void) onTimeout
{
  JSValueRef excp = NULL;
  JSObjectCallAsFunction(timeoutCtx, timeoutFunc, NULL, 0, 0, &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);
  }
}

Função nativa para avaliação de 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, NULL, NULL, 0, &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;
}

Após toda essa configuração, o mecanismo do JSC deve avaliar isso:

[jsEngine evaluate:@"iosSetTimeout(function(){iosLog('timeout done')}, 5000)"];

A execução do JS chama o nativo_setTimeout, e depois de cinco segundos, o nativoonTimeout é chamado e acidente acontece emJSObjectCallAsFunction. otimeoutCtx torna-se inválido. Parece que o contexto da função timeout é local e, durante o período de tempo, o coletor de lixo exclui esse contexto no lado do JSC.

O interessante é também, se_setTimeout função é alterada para chamarJSObjectCllAsFunction imediatamente, sem esperar pelo tempo limite, ele funciona como esperado.

Como evitar a exclusão automática de contexto em retornos de chamada assíncronos?

questionAnswers(6)

yourAnswerToTheQuestion