假设我想从A类调用B类中的一些方法,B类中有两种方法:
- (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1; + (void) methodTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
objc_msgSend(objc_getClass("ClassB"),sel_registerName("methodTestWithStr1:str2:"),@"111",@"222");
objc_msgSend([[objc_getClass("ClassB") alloc] init],sel_registerName("instanceTestWithStr1:str2:"),@"222");
但问题是得到一个B类实例我必须调用“initWithXXXXX:XXXXXX:XXXXXX”而不是“init”所以
将一些必要的参数传递给B类来做init的东西.所以我在类A中存储了一个ClassB实例作为变量:
self.classBInstance = [[ClassB alloc] initWithXXXXX:XXXXXX:XXXXXX];
问题是,我想通过简单地应用类名和方法sel来调用方法,如“ClassName”和“SEL”,然后动态调用它:
>如果是类方法.然后把它称为:
objc_msgSend(objc_getClass(“ClassName”),sel_registerName(“SEL”));
>如果是实例方法,则在调用类中找到现有的类实例变量,然后:
objc_msgSend([self.classInstance,sel_registerName(“SEL”));
所以我想知道是否有办法:
>检查一个类是否有给定的方法(我发现“responseToSelector”将是一个)
>检查类方法或实例方法中的给定方法(也可以使用responseToSelector)
>检查一个类是否有给定类的实例变量所以我可以调用一个实例方法,如:
objc_msgSend(objc_getClassInstance(self,“ClassB”),sel_registerName(“SEL”));
解决方法
请告诉见面你正在做什么?建立语言之间的桥梁?因为如果不是这样的话,你就可以深入到一个兔子洞里去探索,但这可能不是一个非常有效也不优雅的解决方案.
现在:
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:
- 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);
如果您想要传递参数,请参阅下文.理查德的答案中的NSInvocation是一种高级方法,但间接使用objc_msgSend()(并且NSInvocation有局限性).
“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”));
这没有意义.类没有实例变量.类的实例有一个实例变量,但是你可能需要一个特定的实例,而不是你在这个地方创建的一些随机实例.随着时间的推移,实例会携带状态并使状态更加容易.
在任何情况下,您都可以使用上面的机制轻松调用类上的classInstance方法(这将完全没有意义 – 只需编写[self classInstance]并完成它),并从那里:
id classInstance = [self classInstance]; SEL sel = ... get yer SEL here ...; if ([classInstance respondsToSelector:sel]) objc_msgSend(classInstance,sel);
显然,如果您需要参数,请参阅下文.
So I want to know if there is any way to:
- Check if a class has a given method (I found “responseToSelector” will be the one)
往上看.类响应respondsToSeletor:.如果要检查类的实例是否响应选择器,可以调用instancesRespondToSelector:.
Class klass = ... get yer class on...; SEL someSelector = ... get that SEL ...; if ([klass instancesRespondToSelector:someSelector]) objc_msgSend(instanceOfKlassObtainedFromSomewhere,someSelector);
争论再次?见下文.
“2”. Check if a given method in class method or instance method (maybe can use
responseToSelector
as well)
往上看.给定一个类,您检查一个或多个类是否响应任何给定的选择器.请注意,对于NSObject协议中的许多选择器,类将响应许多NSObject实例方法,因为元类 – 类是其实例的类 – 实现了相当多的所述方法.
“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,sel_registerName(“SEL”));
setter / getter方法和实例变量之间的关系完全是巧合.不需要ivar,也不需要为任何给定的ivar设置定位器和/或吸气剂.因此,这个问题没有意义,因为任意调用基于ivar名称的方法通常会失败.
正如Richard建议的那样,您可以使用键值编码,但这意味着手动装箱传递给setter的值,并手动取消从非get对象类型的getter中检索的值.
在封面下,KVC实现了一种启发式方法,可以在类中搜索方法,或者使用与所请求的名称大致匹配的名称的ivar.主要是因为它会执行搜索_前缀等操作.NSKeyValueCoding.h标头是一个有趣的读取.
无论如何,不需要选择器.给出一个名字,只需:
id foo = [myInstance valueForKey:@"iVarName"];
和:
[myInstance setValue:[NSNumber numberWithInt:42] forKey:@“ivarName”];
显然,打字是一个主要问题.如果你有非对象类型,那么你将不得不处理进入/退出NSValue容器而不是所有东西都适合,这使得你可以对KVC方法/ ivar搜索算法进行逆向工程(不是很难 – 只是一堆字符串操作和查找)然后传递任意参数如下.
请注意,对objc_msgSend()的两次调用在技术上都是错误的,因为它们都没有使用显式参数类型将objc_msgSend()类型转换为非varargs形式.你需要这样的东西:
// - (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1; void (*msgSendVoidStrStr(id,SEL,NSString*,NSString*) = (void*)objc_msgSend; msgSendVoidStrStr(...obj...,@selector(instanceTestWithStr1:str2:),str1,str2);
这是因为varargs ABI和显式参数类型ABI不一定兼容所有体系结构. ARC,IIRC明确强制执行.
还要注意,任意调用类或实例方法的概念,其中调用实例方法实时实例化类的实例确实没有多大意义.但是,嘿……你的代码.
请注意,您也不希望以这种方式调用sel_registerName();如果您要调用选择器,它最好已经存在.该函数显式存在于运行时定义类.最好使用NSSelectorFromString()或sel_getUid()(不幸的是,由于多年来没有纪律的程序员,有效地最终会调用sel_registerName()).至少你的意图是对的.
现在,要根据需要使用objc_msgSend(),您需要回答一个问题,结果答案将完全不同.一个答案是“哦,只做X”的简单路线,另一个是“哦,圣牛,你正走在痛苦的道路上”.
问题:您是否有一组固定的方法签名,或者您是否必须传递多种类型的任意参数集?
最终,有多少种不同的参数将决定代码的复杂程度.如果您只有0,1或2个参数并且它们始终是对象,请坚持使用invokeSelector:,invokeSelector:withObject:和invokeSelector:withObject:withObject:.
如果答案是“固定的一组方法签名”,那么答案就在上面;只需声明一个函数指针,其中包含您要使用的所有不同的可能方法签名,并在运行时选择正确的方法,并按上述方式将其称为函数调用.
现在,如果答案是“具有许多不同参数组合的任意选择器集”,则答案要困难得多.您需要使用libffi(或类似的东西)以编程方式执行编译器在编译msgSendVoidStrStr(… obj …,@ selector(instanceTestWithStr1:str2 :),str2);时所执行的操作. libffi提供了使用几乎任意的参数和返回类型对调用进行编码所需的一切.
它不容易使用.事实上,使用libffi构建自己的堆栈框架已经足够困难了,编写一个转储所有可能的调用组合的脚本并为每个组合创建一个封面函数可能更容易,可能将参数作为NSArray *容器并解码他们内心.像(自动生成)的东西:
void msgSendVoidStrStr(id obj,SEL _cmd,NSArray*args) { objc_msgSend(obj,_cmd,[args objectAtIndex:0],[args objectAtIndex:1]); }
事实证明,这比编写一堆tricksie运行时代码要容易得多.