diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 65026dac..c4820212 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -1,5 +1,6 @@ #include #include +#include #include "ArgConverter.h" #include "NativeScriptException.h" #include "DictionaryAdapter.h" @@ -7,6 +8,7 @@ #include "Interop.h" #include "Helpers.h" #include "Runtime.h" +#include "RuntimeConfig.h" using namespace v8; using namespace std; @@ -27,7 +29,27 @@ bool callSuper = false; if (instanceMethod) { BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver); - tns::Assert(wrapper != nullptr, isolate); + + if (wrapper == nullptr) { + // During fast view churn like HMR in development, JS objects can outlive their + // native wrappers briefly. In Debug, avoid a crash and just skip the native call. + // In Release, assert so crash reporting can capture unexpected cases. + if (RuntimeConfig.IsDebug) { + const char* selectorStr = meta ? meta->selectorAsString() : ""; + const char* jsNameStr = meta ? meta->jsName() : ""; + const char* classNameStr = klass ? class_getName(klass) : ""; + // Suppress duplicate logs: only log once per class+selector for this process. + static std::unordered_set s_logged; + std::string key = std::string(classNameStr) + ":" + selectorStr; + if (s_logged.insert(key).second) { + Log(@"Note: ignore method on non-native receiver (class: %s, selector: %s, jsName: %s, args: %d). Common during HMR.", + classNameStr, selectorStr, jsNameStr, (int)args.Length()); + } + return v8::Undefined(isolate); + } else { + tns::Assert(false, isolate); + } + } if (wrapper->Type() == WrapperType::ObjCAllocObject) { ObjCAllocDataWrapper* allocWrapper = static_cast(wrapper); @@ -43,7 +65,21 @@ // For extended classes we will call the base method callSuper = isMethodCallback && it != cache->ClassPrototypes.end(); } else { - tns::Assert(false, isolate); + if (RuntimeConfig.IsDebug) { + const char* selectorStr = meta ? meta->selectorAsString() : ""; + const char* jsNameStr = meta ? meta->jsName() : ""; + const char* classNameStr = klass ? class_getName(klass) : ""; + // Suppress duplicate logs: only log once per class+selector for this process. + static std::unordered_set s_logged; + std::string key = std::string(classNameStr) + ":" + selectorStr; + if (s_logged.insert(key).second) { + Log(@"Note: ignore receiver wrapper type %d (class: %s, selector: %s, jsName: %s). Common during HMR.", + (int)wrapper->Type(), classNameStr, selectorStr, jsNameStr); + } + return v8::Undefined(isolate); + } else { + tns::Assert(false, isolate); + } } } @@ -878,7 +914,7 @@ Local thiz = args.This(); Isolate* isolate = args.GetIsolate(); BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz); - if (wrapper == nullptr && wrapper->Type() != WrapperType::ObjCObject) { + if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) { return; }