swift 块的 `objc_method_description.types` 的格式是什么?

Rol*_*nge 6 objective-c objective-c-runtime method-signature swift

Swift 编译器为带有块的 swift 方法生成的 Objective-C 方法编码似乎使用了任何地方都没有记录的语法。

例如方法:

func DebugLog2(message: String) async -> String
Run Code Online (Sandbox Code Playgroud)

有方法编码:

v32@0:8@"NSString"16@?<v@?@"NSString">24

并且文档中至少有三个字符("<和)未涵盖:>

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

该类NSMethodSignature也不喜欢这种编码:

[NSMethodSignature signatureWithObjCTypes:"\"NSString\"16@?<v@?@\"NSString\">24"];
Run Code Online (Sandbox Code Playgroud)

结果是:

'+[NSMethodSignaturesignatureWithObjCTypes:]: '"NSString"16@?<v@?@"NSString">24'' 中不支持类型编码规范 '"'

我尝试查看 Swift 的代码,似乎有一种叫做“扩展方法类型编码”的东西:

https://github.com/apple/swift/blob/c2fc7ee9e72e9a39854548e3202c667e4934dc65/test/IRGen/objc_methods.swift#L13-L14

但我在试图找出 Swift 代码库中方法类型编码生成的位置时迷失了方向。

不回答此问题的相关问题:

示例代码:

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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

印刷:

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
Run Code Online (Sandbox Code Playgroud)

但为什么我需要这个?

我需要计算堆栈帧的最大大小,为了做到这一点,我解析方法编码。我需要计算最小大小的原因是因为我在调用实际的 objc_msgSend 之前拦截对 objc_msgSend 的调用以添加 Objective-C 异常处理,所以它类似于:

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
Run Code Online (Sandbox Code Playgroud)

请注意,不可能在 C 中创建通用拦截方法,因此我在汇编代码中完成了它。通用拦截代码需要为原始参数(堆栈帧)分配空间并复制,为了做到这一点,我解析方法编码以找出每个参数有多大。

计数不必精确,只需要最大值,因此将所有参数所需的空间相加并分配那么多堆栈空间就可以了,无需减去寄存器中传递的任何参数的大小。

Rob*_*ier 4

我不相信编码格式是公开记录的。您可以从ASTContext源中计算出大部分内容。Mattt 的类型编码博客文章总结得很好:

\n
\n

那么,我们从对 Objective-C 类型编码的新理解中获得了什么?老实说,没有那么多(除非你\xe2\x80\x99正在做任何疯狂的元编程)。

\n

但正如我们从一开始就说过的,追求破译秘密信息是有智慧的。

\n
\n

我同意,这是一个值得追求的目标。只是不要指望这在 Swift 中那么有用。

\n

针对这个具体情况回答一下:

\n
\n

v32@0:8@"NSString"16@?<v@?@"NSString">24

\n
\n

正如您从之前的研究中知道的那样,这些数字并没有多大意义,因此我们不会担心这些。

\n

语法@"..."是一个对象,其类名用引号括起来。您可以在Type::ObjCObjectPointer

\n
 8501    S += \'@\';\n 8502    if (OPT->getInterfaceDecl() &&\n 8503        (FD || Options.EncodingProperty() || Options.EncodeClassNames())) {\n 8504      S += \'"\';\n 8505      S += OPT->getInterfaceDecl()->getObjCRuntimeNameAsString();\n 8506      for (const auto *I : OPT->quals()) {\n 8507        S += \'<\';\n 8508        S += I->getObjCRuntimeNameAsString();\n 8509        S += \'>\';\n 8510      }\n 8511      S += \'"\';\n 8512    }\n
Run Code Online (Sandbox Code Playgroud)\n

语法@?<...>是块指针,详细信息请参见Type::BlockPointer

\n
 8401  case Type::BlockPointer: {\n 8402    const auto *BT = T->castAs<BlockPointerType>();\n 8403    S += "@?"; // Unlike a pointer-to-function, which is "^?".\n 8404    if (Options.EncodeBlockParameters()) {\n 8405      const auto *FT = BT->getPointeeType()->castAs<FunctionType>();\n 8406 \n 8407      S += \'<\';\n 8408      // Block return type\n 8409      getObjCEncodingForTypeImpl(FT->getReturnType(), S,\n 8410                                 Options.forComponentType(), FD, NotEncodedT);\n 8411      // Block self\n 8412      S += "@?";\n 8413      // Block parameters\n 8414      if (const auto *FPT = dyn_cast<FunctionProtoType>(FT)) {\n 8415        for (const auto &I : FPT->param_types())\n 8416          getObjCEncodingForTypeImpl(I, S, Options.forComponentType(), FD,\n 8417                                     NotEncodedT);\n 8418      }\n 8419      S += \'>\';\n 8420    }\n 8421    return;\n 8422  }\n
Run Code Online (Sandbox Code Playgroud)\n

这是编码的内容如下:

\n
- (void)m:(NSString *)s completion:(void (^)(NSString *))c;\n
Run Code Online (Sandbox Code Playgroud)\n

就是这样async方法被转换为 ObjC 的方式:添加一个接受返回值的完成处理程序。

\n

为了好玩,您可以将其扩展为一个async throws函数:

\n
func DebugLog2(message: String) async throws -> String\n
Run Code Online (Sandbox Code Playgroud)\n

结果将是:

\n
v32@0:8@"NSString"16@?<v@?@"NSString"@"NSError">24\n
Run Code Online (Sandbox Code Playgroud)\n

这相当于:

\n
- (void)m:(NSString *)s completion:(void (^)(NSString *, NSError *))c;\n
Run Code Online (Sandbox Code Playgroud)\n

(我应该清楚,当我在这里说“等效”时,我并不是说它实际上与调用method_getTypeEncoding这些方法相同。method_getTypeEncoding不返回扩展编码。您会得到v32@0:8@16@?24这两个: void 返回方法,接受一个对象 (@) 和一个块 (@?)。但这些是 Swift 编码所描述的方法。)

\n

顺便说一句,NSMethodSignature 绝对可以解析结果。您刚刚将其更改为无效字符串(您删除了返回类型):

\n
const char *types = "v32@0:8@\\"NSString\\"16@?<v@?@\\"NSString\\">24";\nNSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:types];\nprintf("%s\\n", [signature getArgumentTypeAtIndex:3]);\n\n// Outputs: @?<v@?@"NSString">\n
Run Code Online (Sandbox Code Playgroud)\n