What is an IMP, and how can you call one without sending a message?
Predict first
What does this print?
final class Shape: NSObject { @objc dynamic func name() -> String { "shape" } }
typealias NameFn = @convention(c) (AnyObject, Selector) -> NSString
let m = class_getInstanceMethod(Shape.self, #selector(Shape.name))!
let fn = unsafeBitCast(method_getImplementation(m), to: NameFn.self)
print(fn(Shape(), #selector(Shape.name)))
- shape
- Optional("shape")
- A crash: IMP cannot be called from Swift
Mental model. A message send asks the switchboard to connect you; an IMP is the direct-dial number you wrote down. It skips the operator but you have to know who answers and what to say.
An IMP is the raw C function pointer behind a method: id (*)(id self, SEL _cmd, ...). method_getImplementation returns it and class_getMethodImplementation or -methodForSelector: look it up by selector. objc_msgSend does exactly that lookup (through a per-class cache) and then jumps to the IMP, so calling the pointer yourself skips the lookup. Before 2019 code did this in hot loops. It is also how a swizzled method reaches the original: you save the old IMP and call it from the new one.
The pointer is untyped, so you cast it to the exact function type with unsafeBitCast(imp, to: (@convention(c) (AnyObject, Selector) -> NSString).self) and pass self and the selector yourself. A mismatch in arguments or return type is not diagnosed (see the type-encoding question) and a struct return needs objc_msgSend_stret-style rules on some architectures. In practice the cache makes a normal message send cheap, so direct IMP calls are for runtime tooling rather than speed.
The function pointer is the compiled method; calling it with the object and selector runs it directly. The Swift String result comes back as the object it bridges to.
Common traps
- Casting to the wrong function type. The call compiles and then returns garbage or crashes.
- Holding an IMP across a swizzle. You keep the old code, which is the point when you want the original and a bug when you wanted the new one.
They might follow up with
- How does
perform(_:)relate? It sends a message, so the lookup and any forwarding still happen. - What does an IMP created from a block look like?
imp_implementationWithBlockgives a function that takesselfand drops_cmd.
Related questions
- 4.1.1 What happens when you send a message in Objective-C, and what if nothing responds?
- 4.6.1 How does method swizzling work, and why does it need dynamic in Swift?
- 4.10.4 How do you create or retarget a class at run time, and why does Swift sometimes ignore it?
← What is a method type encoding, and why does a wrong signature crash… How do you create or retarget a class at run time, and why does Swift… →