Skip to content

Commit fe9e2ee

Browse files
committed
perf(ffi): cache prepared ObjC invocation per arg count in method host fn
Parse the metadata/ObjC signatures once per (method, arg count) and reuse the prepared invocation, instead of re-parsing on every call.
1 parent bec6706 commit fe9e2ee

1 file changed

Lines changed: 51 additions & 33 deletions

File tree

NativeScript/ffi/shared/bridge/HostObjects.mm

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)