iOS アプリケーション内で JavaScriptCore ライブラリを使用しており、setTimeout 関数を実装しようとしています。
setTimeout(func, period)
アプリケーションの起動後、グローバル コンテキストを持つ JSC エンジンが作成され、そのコンテキストに 2 つの関数が追加されます。
_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), nameRef, 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, 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);
}
}
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;
}
そのセットアップ全体の後、JSC エンジンはこれを評価する必要があります。
[jsEngine evaluate:@"iosSetTimeout(function(){iosLog('timeout done')}, 5000)"];
JS の実行でネイティブが呼び出され_setTimeout
、5 秒後にネイティブonTimeout
が呼び出されて でクラッシュが発生しJSObjectCallAsFunction
ます。はtimeoutCtx
無効になります。タイムアウト関数のコンテキストはローカルのように聞こえ、その間、ガベージコレクターはJSC側でそのコンテキストを削除します。
興味深いことに、タイムアウトを待たずにすぐ_setTimeout
に呼び出すように関数を変更すると、期待どおりに動作します。JSObjectCllAsFunction
このような非同期コールバックでの自動コンテキスト削除を防ぐにはどうすればよいですか?