什么时候在Objective-C ++中销毁C ++对象?

时间:2019-05-11 23:11:56

标签: objective-c swift objective-c++

我试图通过创建一个Object-C ++类来包装它来公开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触发得太早,因此可以在调用.toString()之前清除我的V8结果。

这引起了一个普遍的问题: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 个答案:

答案 0 :(得分:2)

问题的很大一部分是,尽管NJSRef m;的声明未声明实例变量,即使它位于@implementation中。这只是全局的文件作用域。只有一个,并且NJSValue的所有实例都共享(并破坏了它)。您必须将其括在花括号{...}中,以使其成为实例变量。

这解释了为什么至少从不销毁它。也许还有很多其他症状,但是很难确定您所使用的外部类型是我不熟悉的。