I have a class method returning a CGSize
and I'd like to call it via the Objective-C runtime functions because I'm given the class and method names as string values.
I'm compiling with ARC flags in XCode 4.2.
Method signature:
+(CGSize)contentSize:(NSString *)text;
The first thing I tried was to invoke it with objc_msgSend
like this:
Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");
id result = objc_msgSend(clazz, method, text);
This crashed with "EXC_BAD_ACCESS" and without a stack trace. I used this first because the documentation for objc_msgSend
says,
When it encounters a method call, the compiler generates a call to one of the functions
objc_msgSend
,objc_msgSend_stret
,objc_msgSendSuper
, orobjc_msgSendSuper_stret
. [...] Methods that have data structures as return values are sent usingobjc_msgSendSuper_stret
andobjc_msgSend_stret
.
Next, I used objc_msgSend_stret
like this:
Class clazz = NSClassFromString(@"someClassName);
SEL method = NSSelectorFromString(@"contentSize:");
CGSize size = CGSizeMake(0, 0);
objc_msgSend_stret(&size, clazz, method, text);
Using the above signature gives me the following two compiler errors and two warnings:
error: Automatic Reference Counting Issue: Implicit conversion of a non-Objective-C pointer type 'CGSize *' (aka 'struct CGSize *') to 'id' is disallowed with ARC
warning: Semantic Issue: Incompatible pointer types passing 'CGSize *' (aka 'struct CGSize *') to parameter of type 'id'
error: Automatic Reference Counting Issue: Implicit conversion of an Objective-C pointer to 'SEL' is disallowed with ARC
warning: Semantic Issue: Incompatible pointer types passing '__unsafe_unretained Class' to parameter of type 'SEL'
If I look at the declaration of the method, it is:
OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
which is identical to objc_msgSend
:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
This explains the compiler errors to me but what do I use at the run-time level to invoke a class and its static method at run-time and return a struct value?
You need to cast objc_msgSend_stret
to the correct function pointer type. It's defined as void objc_msgSend_stret(id, SEL, ...)
, which is an inappropriate type to actually call. You'll want to use something like
CGSize size = ((CGSize(*)(id, SEL, NSString*))objc_msgSend_stret)(clazz, @selector(contentSize:), text);
Here we're just casting objc_msgSend_stret to a function of type (CGSize (*)(id, SEL, NSString*))
, which is the actual type of the IMP
that implements +contentSize:
.
Note, we're also using @selector(contentSize:)
because there's no reason to use NSSelectorFromString()
for selectors known at compile-time.
Also note that casting the function pointer is required even for regular invocations of objc_msgSend()
. Even if calling objc_msgSend()
directly works in your particular case, it's relying on the assumption that the varargs method invocation ABI is the same as the ABI for calling a non-varargs method, which may not be true on all platforms.
If you wish to retrieve a struct from your class method, you can use an NSInvocation as follows:
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];
At the end of this, returnStruct
should contain your struct value. I just tested this in an ARC-enabled application and this works fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With