@@ -1215,51 +1215,69 @@ throw JSError(runtime,
12151215 // through the property interceptor is not).
12161216 if (hasMethodMember (members, property, false )) {
12171217 auto bridge = bridge_;
1218- id object = object_;
12191218 std::weak_ptr<NativeApiObjectHostObject> weakSelf =
12201219 shared_from_this ();
12211220 std::string memberName = property;
1221+ // Cache the prepared invocation per argument count so the metadata
1222+ // and ObjC signatures are parsed once instead of on every call.
1223+ auto preparedCache = std::make_shared<std::unordered_map<
1224+ size_t ,
1225+ std::pair<std::string,
1226+ std::shared_ptr<NativeApiPreparedObjCInvocation>>>>();
12221227 Value methodFunction = Function::createFromHostFunction (
12231228 runtime, PropNameID::forAscii (runtime, property.c_str ()), 0 ,
1224- [bridge, object, weakSelf, memberName](
1229+ [bridge, weakSelf, memberName, preparedCache ](
12251230 Runtime& runtime, const Value&, const Value* args,
12261231 size_t count) -> Value {
1227- const NativeApiSymbol* symbol =
1228- bridge->findClassForRuntimeClass (object_getClass (object));
1229- if (symbol == nullptr ) {
1230- throw JSError (
1231- runtime,
1232- " Objective-C metadata is not available for object." );
1232+ auto self = weakSelf.lock ();
1233+ if (!self || self->object_ == nil ) {
1234+ throw JSError (runtime,
1235+ " Cannot send Objective-C selector to nil." );
12331236 }
1234- const NativeApiMember* selected = selectMethodMember (
1235- bridge->membersForClass (*symbol), memberName, false , count);
1236- if (selected == nullptr ) {
1237- // NSError-out methods (selector ending in "error:") may be
1238- // called with the trailing error argument omitted.
1237+ auto cached = preparedCache->find (count);
1238+ if (cached == preparedCache->end ()) {
1239+ const NativeApiSymbol* symbol =
1240+ bridge->findClassForRuntimeClass (
1241+ object_getClass (self->object_ ));
1242+ if (symbol == nullptr ) {
1243+ throw JSError (
1244+ runtime,
1245+ " Objective-C metadata is not available for object." );
1246+ }
12391247 const auto & classMembers = bridge->membersForClass (*symbol);
1240- if (const NativeApiMember* withError = selectMethodMember (
1241- classMembers, memberName, false , count + 1 )) {
1242- const std::string& sel = withError->selectorName ;
1243- if (sel.size () >= 6 &&
1244- sel.compare (sel.size () - 6 , 6 , " error:" ) == 0 ) {
1245- selected = withError;
1248+ const NativeApiMember* selected =
1249+ selectMethodMember (classMembers, memberName, false , count);
1250+ if (selected == nullptr ) {
1251+ // NSError-out methods (selector ending in "error:") may be
1252+ // called with the trailing error argument omitted.
1253+ if (const NativeApiMember* withError = selectMethodMember (
1254+ classMembers, memberName, false , count + 1 )) {
1255+ const std::string& sel = withError->selectorName ;
1256+ if (sel.size () >= 6 &&
1257+ sel.compare (sel.size () - 6 , 6 , " error:" ) == 0 ) {
1258+ selected = withError;
1259+ }
12461260 }
12471261 }
1262+ if (selected == nullptr ) {
1263+ throw JSError (
1264+ runtime,
1265+ " Objective-C selector is not available for the provided "
1266+ " arguments count: " +
1267+ memberName);
1268+ }
1269+ auto prepared = prepareNativeApiObjCInvocation (
1270+ runtime, bridge, object_getClass (self->object_ ), false ,
1271+ selected->selectorName , selected);
1272+ cached = preparedCache
1273+ ->emplace (count, std::make_pair (
1274+ selected->selectorName ,
1275+ std::move (prepared)))
1276+ .first ;
12481277 }
1249- if (selected == nullptr ) {
1250- throw JSError (
1251- runtime,
1252- " Objective-C selector is not available for the provided "
1253- " arguments count: " +
1254- memberName);
1255- }
1256- if (auto self = weakSelf.lock ()) {
1257- return self->callObjectSelector (
1258- runtime, selected->selectorName , selected, args, count);
1259- }
1260- return callObjCSelector (runtime, bridge, object, false ,
1261- selected->selectorName , selected, args,
1262- count);
1278+ return self->callPreparedObjectSelector (
1279+ runtime, cached->second .first , *cached->second .second , args,
1280+ count);
12631281 });
12641282 // Cache the resolved host function so repeated method access does not
12651283 // reallocate it on every call (hot path).
0 commit comments