The Objective-C method encoding produced by the Swift compiler for swift methods with blocks seems to use syntax not documented anywhere.
For instance the method:
func DebugLog2(message: String) async -> String
has the method encoding:
v32@0:8@"NSString"16@?<v@?@"NSString">24
and there are at least three characters (", <, and >) not covered in the documentation:
The NSMethodSignature class also doesn't like this encoding:
[NSMethodSignature signatureWithObjCTypes:"\"NSString\"16@?<v@?@\"NSString\">24"];
results in:
'+[NSMethodSignature signatureWithObjCTypes:]: unsupported type encoding spec '"' in '"NSString"16@?<v@?@"NSString">24''
I tried looking through Swift's code, and it seems there's something called "extended method type encodings":
but I got lost trying to figure out where in the Swift codebase the method type encoding is generated.
Related questions that don't answer this:
- How to decipher "objc_method_description" from protocol method description list?
- What are the digits in an ObjC method type encoding string?
Sample code:
decodeProto()
@objc
class TestClass : NSObject {
@objc
func DebugLog(message: String) -> String {
print(message)
return message
}
@objc
func DebugLog2(message: String) async -> String {
do {
try await Task.sleep(nanoseconds: 1_000_000_000)
} catch {}
print(message);
return message;
}
}
func decodeProto() {
var methodCount : UInt32 = 1
let methodList = class_copyMethodList(TestClass.self, UnsafeMutablePointer<UInt32>(&methodCount))!
for i in 0..<Int(methodCount) {
let method = methodList[i];
let desc = method_getDescription (method);
let name = desc.pointee.name!
let types = String(validatingUTF8: desc.pointee.types!)!
print("Selector: \(name) Description: \(types)")
}
}
prints:
Selector: DebugLog2WithMessage:completionHandler: Description: v32@0:8@"NSString"16@?<v@?@"NSString">24
Selector: DebugLogWithMessage: Description: @24@0:8@16
Selector: init Description: @16@0:8
But why do I need this?
I need to compute a maximum size for the stack frame, and in order to do that I parse the method encoding. The reason I need to compute the minimum size is because I intercept calls to objc_msgSend to add Objective-C exception handling before I call the actual objc_msgSend, so it's something like:
intercept_objc_msgSend (...) {
@try {
objc_msgSend (...);
} @catch (NSException *ex) {
handleException (ex);
}
}
Note that it's not possible to create a generic interception method in C, so I've done it in assembly code. The generic interception code needs to allocate space for and copy the original arguments (stack frame), and in order to do that I parse the method encoding to figure out how big each argument is.
The count doesn't have to be exact, only a maximum is needed, so adding up the space required for all the arguments and allocating that much stack space works just fine, there's no need to subtract the size for any argument passed in registers.
I don't believe the encoding format is publicly documented. You can work out most of it from the ASTContext source. Mattt's Type Encodings blog post sums it up well:
And I agree, it's a worthy pursuit. Just don't expect this to be all that useful in Swift.
To answer this specific situation:
As you know from your previous research, the numbers don't mean much, so we won't worry about those.
The syntax
@"..."is an object with the class name given in quotes. You can find that in theType::ObjCObjectPointercase:The syntax
@?<...>is a block pointer, as detailed in theType::BlockPointercase:What this is encoding is the following:
This is how
asyncmethods are translated to ObjC: a completion handler is added that accepts the return value.For fun, you can extend this to an
async throwsfunction:And the result will be:
Which is equivalent to:
(I should be clear when I say "equivalent" here, I don't mean it would literally be the same as calling
method_getTypeEncodingon these methods.method_getTypeEncodingdoesn't return extended encodings. You'd getv32@0:8@16@?24for both of these: a void-returning method that takes an object (@) and a block (@?). But these are the methods being described by the Swift encodings.)As a side note, NSMethodSignature can definitely parse the result. You just changed it to an invalid string (you removed the return type):