Swift编译器为带块的Swift方法生成的Objective-C方法编码似乎使用了任何地方都没有记录的语法。
例如该方法:
func DebugLog2(message: String) async -> String
具有编码的方法:
v32@0:8@“NSString“16@?<v@?@“NSString”>24
并且至少有三个字符("
、<
和>
)未在文档中涵盖:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.htmlNSMethodSignature
类也不喜欢这种编码:
[NSMethodSignature signatureWithObjCTypes:"\"NSString\"16@?<v@?@\"NSString\">24"];
结果:
'+[NSMethodSignature signatureWithObjCTypes:]:““NSString“16@中不支持的类型编码规范"?<v@?@“NSString”>24''
我试着浏览Swift的代码,似乎有一种叫做“扩展方法类型编码”的东西:
https://github.com/apple/swift/blob/c2fc7ee9e72e9a39854548e3202c667e4934dc65/test/IRGen/objc_methods.swift#L13-L14
但是我在试图弄清楚Swift代码库中的方法类型编码是在哪里生成的时迷路了。
没有回答这个问题的相关问题:
- How to decipher "objc_method_description" from protocol method description list?
- What are the digits in an ObjC method type encoding string?
样本代码:
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)")
}
}
打印:
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
但我为什么需要这个?
我需要计算堆栈帧的最大大小,为此我解析了方法编码。我需要计算最小大小的原因是因为我在调用实际的objc_msgSend之前拦截了对objc_msgSend的调用,以添加Objective-C异常处理,所以它是这样的:
intercept_objc_msgSend (...) {
@try {
objc_msgSend (...);
} @catch (NSException *ex) {
handleException (ex);
}
}
请注意,在C中不可能创建泛型拦截方法,因此我在汇编代码中完成了它。通用拦截代码需要为原始参数(堆栈帧)分配空间并复制原始参数,为了做到这一点,我解析了方法编码,以确定每个参数有多大。
计数不一定是精确的,只需要一个最大值,所以把所有参数所需的空间加起来并分配这么多堆栈空间就可以了,不需要减去传入寄存器的任何参数的大小。
1条答案
按热度按时间lsmepo6l1#
我不相信编码格式是公开的文档。您可以从ASTContext源代码中计算出大部分内容。Mattt的Type Encodings blog post很好地总结了这一点:
那么,我们从对Objective-C类型编码的新理解中获得了什么呢?老实说,没有那么多(除非你正在做任何疯狂的元编程)。
但正如我们从一开始就说过的,在破译秘密信息的过程中存在着智慧。
我同意,这是一个值得追求的目标。只是不要指望这在Swift中有那么大的用处。
要回答此特定情况:
v32@0:8@“NSString“16@?<v@?@“NSString”>24
正如你从之前的研究中所知道的,这些数字并不意味着什么,所以我们不会担心这些。
语法
@"..."
是一个类名用引号括起来的对象。你可以在Type::ObjCObjectPointer
的例子中发现:语法
@?<...>
是一个块指针,如Type::BlockPointer
案例中详细说明的那样:这是什么编码如下:
下面是如何将
async
方法转换为ObjC:添加接受返回值的完成处理程序。为了好玩,你可以将其扩展为一个
async throws
函数:结果将是:
这相当于:
(我应该清楚,当我在这里说“等效”时,我并不是说它实际上与在这些方法上调用
method_getTypeEncoding
相同。method_getTypeEncoding
不返回扩展编码。你会得到v32@0:8@16@?24
这两个:一个void返回方法,它接受一个对象(@)和一个块(@?)。但这些是Swift编码描述的方法。)附带说明一下,NSMethodSignature绝对可以解析结果。你只是把它改成了一个无效的字符串(你删除了返回类型):