如何从运行时返回结构值?
我有一个返回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_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
或objc_msgSendSuper_stret
。 [...]将数据结构作为返回值的方法使用objc_msgSendSuper_stret
和objc_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);
使用上述签名会给我以下两个编译器错误和两个警告:
错误:自动引用计数问题:将非Objective-C指针类型'CGSize *'(又名'struct CGSize *')隐式转换为'id'不允许使用ARC
警告:语义问题:不兼容的指针类型将'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);
这向我解释了编译器错误,但是我在运行时使用什么来在运行时调用一个类及其静态方法并返回结构值?
您需要将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
的实际类型+contentSize:
。
请注意,我们还使用@selector(contentSize:)
因为没有理由将NSSelectorFromString()
用于编译时已知的选择器。
还要注意,即使对于objc_msgSend()
常规调用,也需要强制转换函数指针。 即使调用objc_msgSend()
直接适用于您的特定情况,它依赖于可变参数方法调用ABI与调用非可变参数方法的ABI相同,这在所有平台上可能都不是这样。
如果你想从你的类方法中检索一个结构体,你可以使用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的应用程序中测试了这个,并且这个工作正常。