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

lyfkaqu1  于 2023-09-30  发布在  Swift
关注(0)|答案(1)|浏览(112)

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.html
NSMethodSignature类也不喜欢这种编码:

[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代码库中的方法类型编码是在哪里生成的时迷路了。
没有回答这个问题的相关问题:

样本代码:

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中不可能创建泛型拦截方法,因此我在汇编代码中完成了它。通用拦截代码需要为原始参数(堆栈帧)分配空间并复制原始参数,为了做到这一点,我解析了方法编码,以确定每个参数有多大。
计数不一定是精确的,只需要一个最大值,所以把所有参数所需的空间加起来并分配这么多堆栈空间就可以了,不需要减去传入寄存器的任何参数的大小。

lsmepo6l

lsmepo6l1#

我不相信编码格式是公开的文档。您可以从ASTContext源代码中计算出大部分内容。Mattt的Type Encodings blog post很好地总结了这一点:
那么,我们从对Objective-C类型编码的新理解中获得了什么呢?老实说,没有那么多(除非你正在做任何疯狂的元编程)。
但正如我们从一开始就说过的,在破译秘密信息的过程中存在着智慧。
我同意,这是一个值得追求的目标。只是不要指望这在Swift中有那么大的用处。
要回答此特定情况:
v32@0:8@“NSString“16@?<v@?@“NSString”>24
正如你从之前的研究中所知道的,这些数字并不意味着什么,所以我们不会担心这些。
语法@"..."是一个类名用引号括起来的对象。你可以在Type::ObjCObjectPointer的例子中发现:

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

语法@?<...>是一个块指针,如Type::BlockPointer案例中详细说明的那样:

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

这是什么编码如下:

- (void)m:(NSString *)s completion:(void (^)(NSString *))c;

下面是如何将async方法转换为ObjC:添加接受返回值的完成处理程序。
为了好玩,你可以将其扩展为一个async throws函数:

func DebugLog2(message: String) async throws -> String

结果将是:

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

这相当于:

- (void)m:(NSString *)s completion:(void (^)(NSString *, NSError *))c;

(我应该清楚,当我在这里说“等效”时,我并不是说它实际上与在这些方法上调用method_getTypeEncoding相同。method_getTypeEncoding不返回扩展编码。你会得到v32@0:8@16@?24这两个:一个void返回方法,它接受一个对象(@)和一个块(@?)。但这些是Swift编码描述的方法。)
附带说明一下,NSMethodSignature绝对可以解析结果。你只是把它改成了一个无效的字符串(你删除了返回类型):

const char *types = "v32@0:8@\"NSString\"16@?<v@?@\"NSString\">24";
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:types];
printf("%s\n", [signature getArgumentTypeAtIndex:3]);

// Outputs: @?<v@?@"NSString">

相关问题