What is a method type encoding, and why does a wrong signature crash instead of failing to compile?

4.10.2 · L5 · Learning

Predict first

What type encodings does the runtime report for these three methods?

final class Shape: NSObject {
    @objc var sides = 3
    @objc func area(scale: Double) -> Double { scale }
    @objc dynamic func name() -> String { "shape" }
}
func enc(_ s: Selector) -> String { String(cString: method_getTypeEncoding(class_getInstanceMethod(Shape.self, s)!)!) }
print(enc(#selector(Shape.area(scale:))), enc(#selector(getter: Shape.sides)), enc(#selector(Shape.name)))
  • d24@0:8d16 q16@0:8 @16@0:8
  • v16@0:8 i16@0:8 @16@0:8
  • d24@0:8d16 B16@0:8 *16@0:8

Mental model. The encoding is a shipping label on a crate saying “one box, one barrel”. Nobody opens the crate to check, so a wrong label only shows up when someone unpacks it.

The runtime does not store parameter types, it stores a type-encoding string per method: the return type, the total frame size, then each argument with its offset. v16@0:8 reads “returns void, 16 bytes of arguments: object at offset 0, selector at 8”. Every method has two hidden leading arguments (self as @ and _cmd as :). Letters stand for types: q is a 64-bit integer, d a double, @ any object, B a bool. @encode(T) prints one in Objective-C, and method_getTypeEncoding reads it back.

The runtime uses the string for forwarding (NSMethodSignature, NSInvocation), for class_addMethod and for method_setImplementation, but it does not check that a function matches it. If you install an implementation with a different argument list, or call an IMP through the wrong function type, the registers hold something else and you get garbage or a crash far from the cause. Swift also changes what you see: a String parameter or result is an object (@) because it bridges to NSString, an Int is q, a Double is d.

The double argument and result are d, the Int getter returns q, and the String result is an object (@).

Common traps

  • Adding a method with the wrong encoding string. Forwarding and message sends read it, and a mismatch corrupts arguments.
  • Assuming a Swift Bool is B. Check the real string before you build a signature.

They might follow up with

  • What do the numbers in d24@0:8d16 mean? 24 bytes of arguments; object at 0, selector at 8, a double at 16.
  • Where else do these strings appear? In property attribute strings (T@"NSString",C,N) and in NSMethodSignature.

Related questions

  • 4.1.3 What are the stages of message forwarding, and when would you use them?
  • 4.6.1 How does method swizzling work, and why does it need dynamic in Swift?
  • 4.6.2 Why is method swizzling considered a footgun, and what do you do instead?

← How do you inspect a class’s methods and properties at run time, and…   What is an IMP, and how can you call one without sending a message? →