【问题标题】:How do I return a struct value from a runtime-defined class method under ARC?如何从 ARC 下的运行时定义的类方法返回结构值?
【发布时间】:2012-01-14 09:34:27
【问题描述】:

我有一个返回 CGSize 的类方法,我想通过 Objective-C 运行时函数调用它,因为我将类和方法名称作为字符串值。

我在 XCode 4.2 中使用 ARC 标志进行编译。

方法签名:

+(CGSize)contentSize:(NSString *)text;

我尝试的第一件事是使用objc_msgSend 调用它,如下所示:

Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");

id result = objc_msgSend(clazz, method, text);

这会因“EXC_BAD_ACCESS”而崩溃,并且没有堆栈跟踪。我首先使用它是因为objc_msgSend 的文档说,

当遇到方法调用时,编译器会生成对函数objc_msgSendobjc_msgSend_stretobjc_msgSendSuperobjc_msgSendSuper_stret 之一的调用。 [...] 将数据结构作为返回值的方法使用 objc_msgSendSuper_stretobjc_msgSend_stret 发送。

接下来,我像这样使用objc_msgSend_stret

Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");

CGSize size = CGSizeMake(0, 0);
objc_msgSend_stret(&size, clazz, method, text);

使用上面的签名会给我以下两个编译器错误和两个警告:

错误:自动引用计数问题:ARC 不允许将非 Objective-C 指针类型“CGSize *”(又名“struct CGSize *”)隐式转换为“id”

警告:语义问题:不兼容的指针类型将“CGSize *”(又名“struct CGSize *”)传递给“id”类型的参数

错误:自动引用计数问题:ARC 不允许将 Objective-C 指针隐式转换为“SEL”

警告:语义问题:不兼容的指针类型传递 '__unsafe_unretained Class' 到 'SEL' 类型的参数

如果我看方法的声明,它是:

OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

等同于objc_msgSend:

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

这向我解释了编译器错误,但我在运行时使用什么来在运行时调用类及其静态方法并返回结构值?

【问题讨论】:

    标签: objective-c struct automatic-ref-counting objective-c-runtime class-method


    【解决方案1】:

    您需要将objc_msgSend_stret 转换为正确的函数指针类型。它被定义为void objc_msgSend_stret(id, SEL, ...),这是一个不适合实际调用的类型。你会想要使用类似的东西

    CGSize size = ((CGSize(*)(id, SEL, NSString*))objc_msgSend_stret)(clazz, @selector(contentSize:), text);
    

    这里我们只是将 objc_msgSend_stret 转换为 (CGSize (*)(id, SEL, NSString*)) 类型的函数,这是实现 +contentSize:IMP 的实际类型。

    注意,我们也使用@selector(contentSize:),因为没有理由对编译时已知的选择器使用NSSelectorFromString()

    还要注意,即使是定期调用objc_msgSend(),也需要转换函数指针。即使在您的特定情况下直接调用objc_msgSend(),它也依赖于假设可变参数方法调用ABI与调用非可变参数方法的ABI相同,这可能并非在所有平台上都是正确的。

    【讨论】:

    • 事实证明,您甚至不想使用 'objc_msgSend_stret' 而是使用您指出的正确转换的 objc_msgSend。谢谢!
    • @JonasGardner:我相信这实际上取决于结构的大小和相关架构。您关心的架构上的CGSize 很可能是一个简单的objc_msgSend()。您需要查阅架构 ABI 文档,以了解哪些结构大小/类型将在寄存器中返回,而不是在堆栈中返回。
    【解决方案2】:

    如果您希望从您的类方法中检索结构,您可以使用 NSInvocation,如下所示:

    Class clazz = NSClassFromString(@"MyClass");
    SEL aSelector = NSSelectorFromString(@"testMethod");
    
    CGSize returnStruct; // Or whatever type you're retrieving
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[clazz methodSignatureForSelector:aSelector]];
    
    [invocation setTarget:clazz];
    [invocation setSelector:aSelector];
    
    [invocation invoke];
    [invocation getReturnValue:&returnStruct];
    

    最后,returnStruct 应该包含你的结构值。我刚刚在支持 ARC 的应用程序中对此进行了测试,效果很好。

    【讨论】:

    • NSInvocation 相当昂贵。它在许多情况下都很有用,但通常有更快的方法来完成您想要的。
    • @KevinBallard - 是的,我只是想我会把它扔在那里,以防有人想要一个比objc_msgSend() 更抽象的解决方案。
    • 很公平。 NSInvocation 还可以消除对于给定方法使用 objc_msgSend()objc_msgSend_stret() 的架构的混淆。
    猜你喜欢
    • 1970-01-01
    • 2014-03-01
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 2016-12-22
    • 1970-01-01
    相关资源
    最近更新 更多