What is a method type encoding, and why does a wrong signature crash instead of failing to compile?
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
BoolisB. Check the real string before you build a signature.
They might follow up with
- What do the numbers in
d24@0:8d16mean? 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 inNSMethodSignature.
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? →