Когда объекты C ++ уничтожаются в Objective-C ++? - PullRequest
0 голосов
/ 12 мая 2019

Я пытаюсь представить объект C ++, создав класс Objective-C ++, чтобы обернуть его.

В конечном счете, в Swift я пытаюсь написать это:

print(JSApplication.eval("'TKTK'")?.toString() ?? "")
print(JSApplication.eval("x = {a: 42}; x")?.toString() ?? "")
print(JSApplication.eval("x")?.get("a")?.toString() ?? "nope");
print(JSApplication.eval("1 + 2")?.toInt32() ?? 0)

Однако, когда я пытаюсь вызвать JSApplication.eval("x")?.get("a")?.toString(), Objective-C вызывает dealloc в моем классе после .get("a"), но до .toString().

Обычно в dealloc я бы вызвал .reset() для общего указателя, который содержит класс. Но так как dealloc срабатывает слишком рано, это очистит мой результат V8 до того, как .toString() может быть вызван на него.

В связи с этим возникает общий вопрос: как Swift / Objective-C решает , когда вызывать dealloc для временного объекта? Для чего-то вроде foo()?.bar()?.baz(), где foo и bar возвращают временные объекты, правильно ли, что оба временных объекта получают сообщение dealloc до вызова baz? Вот что я вижу.

Если это правильное поведение, то как правильно продлить время жизни временного объекта до области, в которой вызывается функция, например, C ++? Это возможно?

Вот моя привязка Objective-C ++. (Я заметил, что сообщения "Destroy 0x ..." вообще не выводятся, поэтому деструкторы C ++, похоже, не запускаются. Должен ли я вызывать их вручную?)


// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
  return *value ? *value : "<string conversion failed>";
}

@interface NJSValue (V8)
- (instancetype)init;
- (instancetype)initWithValue:(Local<Value>)value;
@end

struct NJSRef
{
  std::shared_ptr<Nan::Persistent<Value>> _ref;
  ~NJSRef()
  {
    printf("Destroy 0x%08x\n", (unsigned int)(size_t)_ref.get());
  }
};

@implementation NJSValue (V8)
NJSRef m;

- (instancetype)init
{
  self = [super init];
  return self;
}

- (instancetype)initWithValue:(Local<Value>)value
{
  self = [super init];
  Nan::HandleScope scope;
  Nan::EscapableHandleScope escape;
  m._ref.reset(new Nan::Persistent<Value>(escape.Escape(value)));
  printf("Alloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
  return self;
}
@end

@implementation NJSValue
- (void)dealloc
{
  printf("Dealloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
  //m_ref.reset();
}

- ( NSString * _Nonnull )toString
{
  if (m._ref != nullptr) {
    Nan::HandleScope scope;
    Local<Value> value(Nan::New(*m._ref));
    v8::String::Utf8Value str(JS_ISOLATE(), value);
    const char* cstr = ToCString(str);
    return NJSStringToNSString(JS_STR(cstr));
  } else {
    return @"undefined";
  }
}
- (NSNumber *)toInt32
{
  if (m._ref != nullptr) {
    Nan::HandleScope scope;
    Local<Value> value(Nan::New(*m._ref));
    if (!value->IsInt32()) return nullptr;
    return [NSNumber numberWithInt:TO_INT32(value)];
  } else {
    return nullptr;
  }
}

- (NSNumber *)toNumber
{
  if (m._ref != nullptr) {
    Nan::HandleScope scope;
    Local<Value> value(Nan::New(*m._ref));
    if (!value->IsNumber()) return nullptr;
    return [NSNumber numberWithDouble:TO_DOUBLE(value)];
  } else {
    return nullptr;
  }
}


- (NJSValue * _Nullable __strong)get:(NSString * _Nonnull)key  CF_RETURNS_RETAINED
{
  if (m._ref == nullptr) return nullptr;
  v8::HandleScope scope(JS_ISOLATE());
  v8::EscapableHandleScope handle_scope(JS_ISOLATE());
  Local<Value> value(Nan::New(*m._ref));
  if (!value->IsObject()) return nullptr;
  Local<Object> obj(JS_OBJ(value));
  Local<Value> jsKey(JS_STR([key UTF8String]));
  if (!obj->Has(JS_CONTEXT(), jsKey).FromJust()) return nullptr;
  Local<Value> result(obj->Get(jsKey));
  v8::String::Utf8Value str(JS_ISOLATE(), result);
  const char* cstr = ToCString(str);
  printf("got %s\n", cstr);
  NJSValue* ret = [[NJSValue alloc] initWithValue:handle_scope.Escape(result)];
//  [self associateValue:ret withKey:key];
  return ret;
}
@end

@implementation JSApplication

- (instancetype)init
{
   self = [super init];
   if (self) {
   }
   return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
   self = [super init];
   if (self) {
     self.frame = frame;
   }
   return self;
}

// Executes a string within the current v8 context.
v8::Local<v8::Value>
ExecuteString(v8::Isolate* isolate, v8::Local<v8::String> source,
                   v8::Local<v8::Value> name, bool print_result,
                   bool report_exceptions) {
  v8::EscapableHandleScope handle_scope(isolate);
  v8::TryCatch try_catch(isolate);
  v8::ScriptOrigin origin(name);
  v8::Local<v8::Context> context(isolate->GetCurrentContext());
  v8::Local<v8::Script> script;
  if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
    // Print errors that happened during compilation.
    if (report_exceptions)
      ReportException(isolate, &try_catch);
    return handle_scope.Escape(v8::Undefined(isolate));
  } else {
    v8::Local<v8::Value> result;
    if (!script->Run(context).ToLocal(&result)) {
      assert(try_catch.HasCaught());
      // Print errors that happened during execution.
      if (report_exceptions)
        ReportException(isolate, &try_catch);
      return handle_scope.Escape(v8::Undefined(isolate));
    } else {
      assert(!try_catch.HasCaught());
      if (print_result && !result->IsUndefined()) {
        // If all went well and the result wasn't undefined then print
        // the returned value.
        v8::String::Utf8Value str(isolate, result);
        const char* cstr = ToCString(str);
        printf("eval result: %s\n", cstr);
      }
      return handle_scope.Escape(result);
    }
  }
}


void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch) {
  v8::HandleScope handle_scope(isolate);
  v8::String::Utf8Value exception(isolate, try_catch->Exception());
  const char* exception_string = ToCString(exception);
  v8::Local<v8::Message> message = try_catch->Message();
  if (message.IsEmpty()) {
    // V8 didn't provide any extra information about this error; just
    // print the exception.
    fprintf(stderr, "%s\n", exception_string);
  } else {
    // Print (filename):(line number): (message).
    v8::String::Utf8Value filename(isolate,
                                   message->GetScriptOrigin().ResourceName());
    v8::Local<v8::Context> context(isolate->GetCurrentContext());
    const char* filename_string = ToCString(filename);
    int linenum = message->GetLineNumber(context).FromJust();
    fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
    // Print line of source code.
    v8::String::Utf8Value sourceline(
        isolate, message->GetSourceLine(context).ToLocalChecked());
    const char* sourceline_string = ToCString(sourceline);
    fprintf(stderr, "%s\n", sourceline_string);
    // Print wavy underline (GetUnderline is deprecated).
    int start = message->GetStartColumn(context).FromJust();
    for (int i = 0; i < start; i++) {
      fprintf(stderr, " ");
    }
    int end = message->GetEndColumn(context).FromJust();
    for (int i = start; i < end; i++) {
      fprintf(stderr, "^");
    }
    fprintf(stderr, "\n");
    v8::Local<v8::Value> stack_trace_string;
    if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
        stack_trace_string->IsString() &&
        v8::Local<v8::String>::Cast(stack_trace_string)->Length() > 0) {
      v8::String::Utf8Value stack_trace(isolate, stack_trace_string);
      const char* stack_trace_string = ToCString(stack_trace);
      fprintf(stderr, "%s\n", stack_trace_string);
    }
  }
}


+ (NJSValue *)Eval:(NSString *)string  __attribute((ns_returns_retained))
{
  Isolate* isolate = Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);
  Local<Context> context = isolate->GetCurrentContext();
  v8::Context::Scope context_scope(context);
  const char* str = [string UTF8String];
  Local<Value> result = ExecuteString(
      context->GetIsolate(),
      v8::String::NewFromUtf8(context->GetIsolate(), str,
                              v8::NewStringType::kNormal).ToLocalChecked(),
      JS_STR("JSApplication.Eval"), false, true);

  return [[NJSValue alloc] initWithValue:result];
}

@end

Вот вывод, который я получаю для кода Swift в верхней части этого вопроса:

TKTK
Alloc 0x81d34230
Dealloc 0x81d34230
Alloc 0x81d34270
Dealloc 0x81d34270
TKTK
Alloc 0x81d38230
Dealloc 0x81d38230
[object Object]
Alloc 0x81d38250
got 42
Alloc 0x81d38240
Dealloc 0x81d38240
Dealloc 0x81d38240
42
Alloc 0x81d38250
Dealloc 0x81d38250
3

1 Ответ

2 голосов
/ 12 мая 2019

Большая часть вашей проблемы заключается в том, что ваше объявление NJSRef m; не объявляет переменную экземпляра, даже если оно находится внутри @implementation. Это просто глобальная область видимости файла. Есть только один, и он разделяется (и забивается) всеми вашими экземплярами NJSValue. Вы должны заключить его в фигурные скобки {...}, чтобы сделать его переменной экземпляра.

Это объясняет, почему он никогда не разрушается, по крайней мере. Вероятно, и у многих других симптомов, но трудно сказать, учитывая внешние типы, которые вы используете, с которыми я не знаком.

...