@@ -128,6 +128,58 @@ napi_env resolveClassBuilderEnv(id self) {
128128 })
129129 )" ;
130130
131+ const char * kInstallClassFromStringAliasSource = R"(
132+ (function (global) {
133+ if (global.__nsClassFromStringAliasInstalled === true) {
134+ return;
135+ }
136+
137+ const original = global.NSClassFromString;
138+ if (typeof original !== "function") {
139+ return;
140+ }
141+
142+ Object.defineProperty(global, "__nsOriginalNSClassFromString", {
143+ configurable: true,
144+ enumerable: false,
145+ writable: false,
146+ value: original
147+ });
148+
149+ Object.defineProperty(global, "NSClassFromString", {
150+ configurable: true,
151+ enumerable: true,
152+ writable: true,
153+ value: function (name) {
154+ const cls = original.call(this, name);
155+ if (cls != null) {
156+ try {
157+ const registry = global.__nsConstructorsByObjCClassName;
158+ const stringFromClass = global.NSStringFromClass;
159+ if (registry != null && typeof stringFromClass === "function") {
160+ const runtimeName = stringFromClass(cls);
161+ const constructor = registry[runtimeName];
162+ if (constructor != null) {
163+ return constructor;
164+ }
165+ }
166+ } catch (_) {
167+ }
168+ }
169+
170+ return cls;
171+ }
172+ });
173+
174+ Object.defineProperty(global, "__nsClassFromStringAliasInstalled", {
175+ configurable: true,
176+ enumerable: false,
177+ writable: false,
178+ value: true
179+ });
180+ })
181+ )" ;
182+
131183const char * NSFastEnumerationMethodEncoding () {
132184 static const char * encoding = nullptr ;
133185 if (encoding == nullptr ) {
@@ -856,24 +908,6 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
856908 }
857909 }
858910
859- bool hasOwnOverrides = false ;
860- napi_value overridePropertyNames = nullptr ;
861- napi_get_all_property_names (env, args[0 ], napi_key_own_only, napi_key_skip_symbols,
862- napi_key_numbers_to_strings, &overridePropertyNames);
863- uint32_t overridePropertyCount = 0 ;
864- napi_get_array_length (env, overridePropertyNames, &overridePropertyCount);
865- hasOwnOverrides = overridePropertyCount > 0 ;
866-
867- bool hasExposedMethodsOption = false ;
868- bool hasProtocolsOption = false ;
869- if (hasOptionsObject) {
870- napi_has_named_property (env, options, " exposedMethods" , &hasExposedMethodsOption);
871- napi_has_named_property (env, options, " protocols" , &hasProtocolsOption);
872- }
873-
874- bool shouldReuseExistingClass = false ;
875- Class existingExternalClass = nullptr ;
876-
877911 // Create a class name.
878912 napi_value baseClassName;
879913 napi_get_named_property (env, thisArg, " name" , &baseClassName);
@@ -902,22 +936,23 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
902936 }
903937
904938 std::string newClassName;
939+ bool shouldAliasRequestedName = false ;
905940 if (!requestedName.empty ()) {
906941 newClassName = requestedName;
907942 Class existingClass = objc_lookUpClass (newClassName.c_str ());
908- if (existingClass != nullptr &&
909- class_conformsToProtocol (existingClass, @protocol (ObjCBridgeClassBuilderProtocol))) {
943+ auto nextAvailableClassName = [](const std::string& baseName) {
910944 size_t suffix = 1 ;
911945 std::string candidate;
912946 do {
913- candidate = requestedName + std::to_string (suffix++);
947+ candidate = baseName + std::to_string (suffix++);
914948 } while (objc_lookUpClass (candidate.c_str ()) != nullptr );
915- newClassName = candidate;
916- } else if (existingClass != nullptr && !hasOwnOverrides && !hasExposedMethodsOption &&
917- !hasProtocolsOption) {
918- // Name-only extensions should resolve to an existing external class when present.
919- shouldReuseExistingClass = true ;
920- existingExternalClass = existingClass;
949+ return candidate;
950+ };
951+ if (existingClass != nullptr ) {
952+ newClassName = nextAvailableClassName (requestedName);
953+ shouldAliasRequestedName =
954+ !class_conformsToProtocol (existingClass,
955+ @protocol (ObjCBridgeClassBuilderProtocol));
921956 }
922957 } else {
923958 newClassName = baseClassNameBuf;
@@ -1000,14 +1035,27 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
10001035 napi_get_named_property (env, registryGlobal, " __nsConstructorsByObjCClassName" , &classRegistry);
10011036 }
10021037
1003- if (classRegistry != nullptr ) {
1004- napi_set_named_property (env, classRegistry, newClassName.c_str (), newConstructor);
1038+ napi_value installClassFromStringAliasScript = nullptr ;
1039+ napi_value installClassFromStringAlias = nullptr ;
1040+ if (napi_create_string_utf8 (env, kInstallClassFromStringAliasSource , NAPI_AUTO_LENGTH ,
1041+ &installClassFromStringAliasScript) == napi_ok &&
1042+ napi_run_script (env, installClassFromStringAliasScript, &installClassFromStringAlias) ==
1043+ napi_ok &&
1044+ installClassFromStringAlias != nullptr ) {
1045+ napi_value aliasArgs[] = {registryGlobal};
1046+ if (napi_call_function (env, registryGlobal, installClassFromStringAlias, 1 , aliasArgs,
1047+ nullptr ) != napi_ok) {
1048+ clearPendingException (env);
1049+ }
1050+ } else {
1051+ clearPendingException (env);
10051052 }
10061053
1007- if (shouldReuseExistingClass && existingExternalClass != nullptr ) {
1008- napi_remove_wrap (env, newConstructor, nullptr );
1009- napi_wrap (env, newConstructor, (void *)existingExternalClass, nullptr , nullptr , nullptr );
1010- return newConstructor;
1054+ if (classRegistry != nullptr ) {
1055+ napi_set_named_property (env, classRegistry, newClassName.c_str (), newConstructor);
1056+ if (shouldAliasRequestedName && !requestedName.empty ()) {
1057+ napi_set_named_property (env, classRegistry, requestedName.c_str (), newConstructor);
1058+ }
10111059 }
10121060
10131061 // Use ClassBuilder to create the native class and bridge the methods
0 commit comments