Message Forwarding: Performance Trade-offs Between resolveInstanceMethod: and forwardInvocation:
0 reputation · 15 Feb 2023, 15:28 UTC
0 reputation · 15 Feb 2023, 15:28 UTC
The Objective-C runtime provides a multi-stage pipeline for handling missing selectors. When a message is sent to an object that does not respond to the selector, the runtime first attempts dynamic method resolution via resolveInstanceMethod:, followed by the forwardInvocation: mechanism.
While resolveInstanceMethod: allows for the runtime synthesis of a method implementation to prevent subsequent crashes, forwardInvocation: provides a more flexible redirection via NSInvocation. However, the creation of NSInvocation objects and the associated stack depth increase introduce overhead compared to direct method dispatch or dynamic resolution.
When designing a system that relies on these mechanisms for high-frequency calls, it is unclear where the performance tipping point occurs between these two strategies.
class_addMethod during resolution versus a call handled by forwardInvocation:?NSInvocation object allocation impact execution time in tight loops compared to standard runtime dispatch?34775 reputation · 15 Feb 2023, 22:08 UTC
When a selector is missing, resolveInstanceMethod: lets you add an IMP once; after that the call follows the normal fast dispatch path. forwardInvocation: is only reached if resolution fails and must build and invoke an NSInvocation each time, which adds allocation and messaging overhead.
The performance delta comes from the one‑time cost of adding a method versus the per‑call cost of creating an NSInvocation object, setting its arguments, and invoking it through the runtime.
resolveInstanceMethod: before forwardInvocation:.resolveInstanceMethod: returns YES, the newly added method is cached in the class’s method table, so subsequent invocations bypass forwarding entirely.forwardInvocation: always creates an NSInvocation, incurs allocation and retains the invocation until it is invoked or released.resolveInstanceMethod: to add an IMP for the selector when you can provide the implementation (e.g., using class_addMethod).NO and let forwardInvocation: handle the call, delegating to another object or constructing the result manually.class_getInstanceMethod returns a non‑NULL IMP to confirm caching.What is the expected invocation frequency or latency budget for the selector? If it is called rarely, the per‑call overhead of forwardInvocation: may be acceptable; if it is invoked frequently or in a tight loop, resolveInstanceMethod: is preferred.
Use comments to ask for clarification. Post a solution as an answer.
6,260 reputation · 15 Feb 2023, 18:25 UTC
class_addMethod during resolution yields an IMP the runtime dispatches like any compiled method, so steady-state cost is ordinary objc_msgSend. forwardInvocation: costs more than a single NSInvocation allocation: the runtime first calls methodSignatureForSelector:, then builds the invocation and copies every argument into it before your override runs. Microbenchmarks typically put full forwarding several times slower per call than resolved dispatch, but the exact delta varies with architecture and signature complexity, so measure on your target hardware.
One fast path the discussion missed: when you only need to redirect the message, override forwardingTargetForSelector: (available since macOS 10.6/iOS 4.0). The runtime consults it before creating any NSInvocation and re-sends to the returned receiver, reducing overhead to roughly one extra lookup plus normal dispatch. Reserve the full machinery for cases needing argument inspection or capture; a loop of about ten million sends timed with mach_absolute_time makes the gap easy to verify.