Call instance method with objc_msgSend

supersuraccoon picture supersuraccoon · Sep 19, 2012 · Viewed 7.1k times · Source

I'm trying to use the objc_msgSend method to call some method dynamically. Say I want call some method in Class B from Class A and there are two methods in class B like:

- (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
+ (void) methodTestWithStr1:(NSString *)str1 str2:(NSString *)str1;

And I can call the class method like this in Class A successfully:

objc_msgSend(objc_getClass("ClassB"), sel_registerName("methodTestWithStr1:str2:"), @"111", @"222");

And I can call the instance method like this in Class A successfully as well:

objc_msgSend([[objc_getClass("ClassB") alloc] init], sel_registerName("instanceTestWithStr1:str2:"), @"111", @"222");

But the thing is to get a instance of Class B I have to call "initWithXXXXX:XXXXXX:XXXXXX" instead of "init" so that to pass some necessary parameters to class B to do the init stuff. So I stored a instance of ClassB in class A as variable: self.classBInstance = [[ClassB alloc] initWithXXXXX:XXXXXX:XXXXXX];

And then I call the method like this (successfully):

The problem is, I want to call a method by simply applying the classname and the method sel like "ClassName" and "SEL" and then call it dynamically:

  1. If it's a class method. then call it like: objc_msgSend(objc_getClass("ClassName"), sel_registerName("SEL"));

  2. If it's a instance method, find the existing class instance variable in the calling class then: objc_msgSend([self.classInstance, sel_registerName("SEL"));

So I want to know if there is any way to:

  1. Check if a class has a given method (I found "responseToSelector" will be the one)

  2. Check if a given method in class method or instance method (maybe can use responseToSelector as well)

  3. Check if a class has a instance variable of a given class So I can call a instance method like: objc_msgSend(objc_getClassInstance(self, "ClassB"), sel_registerName("SEL"));

Answer

bbum picture bbum · Sep 19, 2012

You'll probably want to read this. What you are asking is effectively "I want to make a new dispatcher" and to answer that question, you ought to have a thorough understanding of how the existing dispatcher works.

Please tell met that is what you are doing? Building a bridge between languages? Because if not, you are way deep down a rabbit hole that'll be damned interesting to explore, but likely not a terribly efficient nor elegant solution.

Now:

The problem is, I want to call a method by simply applying the classname and the method sel like "ClassName" and "SEL" and then call it dynamically:

  1. If it's a class method. then call it like: objc_msgSend(objc_getClass("ClassName"), sel_registerName("SEL"));
Class klass = objc_getClass("ClassName"); // NSClassFromString(@"ClassName")
SEL sel = sel_getUID("selector"); // NSSelectorFromString(@"selector");
if ( [klass respondsToSelector:sel] )
    objc_msgSend(klass, sel);

If you have arguments you want to pass, see below. NSInvocation in Richard's answer is a high level approach, but is an indirect use of objc_msgSend() (and NSInvocation has limitations).

"2". If it's a instance method, find the existing class instance variable in the calling class then: objc_msgSend([self.classInstance, sel_registerName("SEL"));

That doesn't make sense. A class doesn't have an instance variable. An instance of a class has an instance variable, but then you likely need a specific instance and not some random instance you create in this one spot. Instances carry state and accrete that state over time.

In any case, you can easily call the classInstance method on the class using the mechanism above (which would be completely pointless -- just write [self classInstance] and be done with it) and, from there:

id classInstance = [self classInstance];
SEL sel = ... get yer SEL here ...;
if ([classInstance respondsToSelector:sel])
   objc_msgSend(classInstance, sel);

Obviously, if you need arguments, see below.

So I want to know if there is any way to:

  1. Check if a class has a given method (I found "responseToSelector" will be the one)

See above. Classes responds to respondsToSeletor:. If you want to check to see if instances of the class respond to a selector, you can invoke instancesRespondToSelector:.

Class klass = ... get yer class on...;
SEL someSelector = ... get that SEL ...;
if ([klass instancesRespondToSelector:someSelector])
    objc_msgSend(instanceOfKlassObtainedFromSomewhere, someSelector);

Again, arguments? See below.

"2". Check if a given method in class method or instance method (maybe can use responseToSelector as well)

See above. Given a class, you check to see if the class or instances respond to any given selector. Note that for many selectors in the NSObject protocol, classes will respond to many of the NSObject instance methods because the meta class -- the class that classes are an instance of -- implements quite a few of said methods.

"3". Check if a class has a instance variable of a given class So I can call a instance method like: objc_msgSend(objc_getClassInstance(self, "ClassB"), sel_registerName("SEL"));

The relationship between a setter/getter method and an instance variable is entirely coincidental. There doesn't need to be an ivar, nor does there need to be a setter and/or getter for any given ivar. Thus, this question doesn't make sense because arbitrarily calling a method based on an ivar name will often fail.

As Richard suggests, you can use Key Value Coding, but that will imply manual boxing of values passed to the setter and manual unboxing of values retrieved from the getter for non-object types.

Under the covers, KVC implements a heuristic to search the class for a method or ivar with a name that mostly matches the requested name. Mostly because it'll do things like search for _ prefixes, etc. The NSKeyValueCoding.h header is an interesting read.

In any case, no need for a selector. Given a name, just do:

id foo = [myInstance valueForKey:@"iVarName"];

And:

[myInstance setValue:[NSNumber numberWithInt:42] forKey:@"ivarName"];

Obviously, typing is a major issue. If you have non-object types, then you are going to have to deal with getting 'em in/out of the NSValue containers and not all things will fit, which leaves you with reverse engineering the KVC method/ivar search algorithm (not too hard -- just a bunch of string manipulation and lookups) and then passing the arbitrary arguments as below.


Note that both of your calls to objc_msgSend() are technically wrong because neither is typecasting objc_msgSend() to a non-varargs form with the explicit argument types. You would need something like:

// - (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
void (*msgSendVoidStrStr(id, SEL, NSString*, NSString*) = (void*)objc_msgSend;
msgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2);

This is because the varargs ABI and explicit argument typed ABI is not necessarily compatible on all architectures. ARC, IIRC, enforces this explicitly.


Note also that the notion of arbitrarily calling class or instance methods where calling the instance method instantiates an instance of the class on the fly really doesn't make much sense. But, hey... your code.


Note that you also don't ever want to call sel_registerName() in that fashion; if you are about to call a selector, it better already exist. That function exists explicitly for defining classes at runtime. Best to use NSSelectorFromString() or sel_getUid() (which, unfortunately, effectively ends up calling sel_registerName() because of undisciplined programmers over the years). At least your intentions will be right.


Now, to use objc_msgSend() as you desire requires you to answer one question for which the resulting answers will be radically difference. One answer is an easy route to "oh, just do X", the other is "oh, holy cow, you are heading down a path of pain".

The question: Do you have a fixed set of method signatures or must you pass an arbitrary set of arguments of many types?

Ultimately, how many and how many different kinds of arguments will dictate how complex the code will be. If you only ever have 0,1 or 2 arguments and they are always objects, stick with invokeSelector:, invokeSelector:withObject: and invokeSelector:withObject:withObject:.

If the answer is "fixed set of method signatures" then the answer is above; simply declare a function pointer with all the different possible method signatures you want to use and pick the right one at runtime and call it as a function call as per above.

Now, if the answer is "arbitrary set of selectors with many different combinations of arguments", the answer is much more difficult. You'll need to use libffi (or something like it) to programmatically do what the compiler does when it compiles msgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2);. libffi provides everything you need to encode calls with nearly arbitrary arguments and return types.

It is not easy to use. In fact, constructing your own stack frames using libffi is hard enough that it may be easier to write a script that dumps all the possible combinations of calls and creates a cover function for each combination, potentially taking the arguments as an NSArray* container and decoding them internally. Something like (auto-genned):

void msgSendVoidStrStr(id obj, SEL _cmd, NSArray*args) {
    objc_msgSend(obj, _cmd, [args objectAtIndex:0], [args objectAtIndex:1]);
}

This proves to be significantly easier to debug than writing a bunch of tricksie runtime code.