What is an IMP, and how can you call one without sending a message?

4.10.3 · L5 · Learning

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_implementationWithBlock gives a function that takes self and 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… →