iOS-Implementierung von „window.setTimeout“ mit JavascriptCore

Ich verwende die JavaScriptCore-Bibliothek in einer iOS-Anwendung und versuche, die setTimeout-Funktion zu implementieren.

setTimeout(func, period)

Nach dem Start der Anwendung wird die JSC-Engine mit globalem Kontext erstellt und diesem Kontext werden zwei Funktionen hinzugefügt:

_JSContext = JSGlobalContextCreate(NULL);

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

Die folgende native Implementierung ordnet die globale JS-Funktion mit dem gewünschten Namen der statischen Ziel-C-Funktion zu:

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

Und hier ist die Implementierung der Ziel-C-Funktion 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);
}

Funktion, die nach einiger Zeit auf jsEngine aufgerufen werden soll:

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

Native Funktion zur Javascript-Auswertung:

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

Nach diesem gesamten Setup sollte die JSC-Engine Folgendes auswerten:

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

Die JS-Ausführung ruft die native auf_setTimeoutund nach fünf Sekunden der EingeboreneonTimeout wird aufgerufen und Absturz passiert inJSObjectCallAsFunction. DastimeoutCtx wird ungültig. Klingt so, als wäre der Kontext der Zeitüberschreitungsfunktion lokal und während des Zeitraums löscht der Garbage Collector diesen Kontext auf der JSC-Seite.

Das Interessante ist auch, wenn_setTimeout Funktion wird geändert, um anzurufenJSObjectCllAsFunction sofort, ohne auf ein Timeout zu warten, funktioniert es dann wie erwartet.

Wie kann das automatische Löschen von Kontexten in solchen asynchronen Rückrufen verhindert werden?

Antworten auf die Frage(6)

Ihre Antwort auf die Frage