您可能想要read this。您要问的实际上是“我想制作一个新的调度程序”,要回答 那个 问题,您应该对现有调度程序的工作原理有一个透彻的了解。
请告诉meet这是你在做什么?在语言之间架起一座桥梁?因为如果不是这样,那么您将陷入一个非常有趣的兔子洞,但可能不是一个非常有效或优雅的解决方案。
现在:
问题是,我想通过简单地应用
类名和方法 sel 像 "ClassName" 和 "SEL" 然后调用
它是动态的:
- 如果它是一个类方法。然后像这样称呼它:
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”。如果是实例方法,则在调用类中找到现有的类实例变量,然后:
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);
显然,如果您需要参数,请参见下文。
所以我想知道有没有办法:
- 检查一个类是否有给定的方法(我发现“responseToSelector”就是那个)
见上文。类响应respondsToSeletor:。如果你想检查类的实例是否响应选择器,你可以调用instancesRespondToSelector:。
Class klass = ... get yer class on...;
SEL someSelector = ... get that SEL ...;
if ([klass instancesRespondToSelector:someSelector])
objc_msgSend(instanceOfKlassObtainedFromSomewhere, someSelector);
再次,争论?见下文。
“2”。检查类方法或实例方法中的给定方法(可能也可以使用responseToSelector)
见上文。给定一个类,您检查该类或实例是否响应任何给定的选择器。请注意,对于 NSObject 协议中的许多选择器,类将响应许多 NSObject 实例方法,因为 元类 - 类是其实例的类 - 实现了相当多的上述方法.
“3”。检查一个类是否具有给定类的实例变量所以我可以调用一个实例方法,如:
objc_msgSend(objc_getClassInstance(self, "ClassB"), sel_registerName("SEL"));
setter/getter 方法与实例变量之间的关系完全是巧合。不需要 ivar,也不需要任何给定的 ivar 的 setter 和/或 getter。因此,这个问题没有意义,因为随意调用基于 ivar 名称的方法通常会失败。
正如 Richard 建议的那样,您可以使用键值编码,但这意味着手动装箱传递给 setter 的值以及手动取消装箱从 getter 检索到的非对象类型的值。
在幕后,KVC 实现了一种启发式方法,以在类中搜索名称与请求名称大部分匹配的方法或 ivar。主要是因为它会做一些事情,比如搜索 _ 前缀等。NSKeyValueCoding.h 标头是一个有趣的阅读。
在任何情况下,都不需要选择器。给定一个名字,做:
id foo = [myInstance valueForKey:@"iVarName"];
还有:
[myInstance setValue:[NSNumber numberWithInt:42] forKey:@"ivarName"];
显然,打字是一个主要问题。如果您有非对象类型,那么您将不得不处理让它们进出 NSValue 容器的问题,并且并非所有东西都适合,这使您不得不对 KVC 方法/ivar 搜索算法进行逆向工程(不太hard -- 只是一堆字符串操作和查找),然后传递任意参数,如下所示。
请注意,您对objc_msgSend() 的两次调用在技术上都是错误的,因为两者都没有将objc_msgSend() 类型转换为具有显式参数类型的非可变参数形式。你需要类似的东西:
// - (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
void (*msgSendVoidStrStr(id, SEL, NSString*, NSString*) = (void*)objc_msgSend;
msgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2);
这是因为可变参数 ABI 和显式参数类型 ABI 不一定在所有架构上都兼容。 ARC,IIRC,明确地强制执行。
还要注意,任意调用类或实例方法(其中调用实例方法会即时实例化类的实例)的概念实际上没有多大意义。但是,嘿……你的代码。
请注意,您也永远不想以这种方式致电sel_registerName();如果你要调用一个选择器,它最好已经存在。该函数显式存在用于在运行时定义类。最好使用NSSelectorFromString() 或sel_getUid()(不幸的是,由于多年来没有纪律的程序员,实际上最终调用sel_registerName())。至少你的意图是正确的。
现在,如您所愿使用objc_msgSend(),您需要回答一个问题,而得到的答案将完全不同。一个答案是“哦,做X”的简单路线,另一个是“哦,天哪,你正走在一条痛苦的道路上”。
问题:您是否有一组固定的方法签名,还是必须传递多种类型的任意参数集?
最终,有多少和多少不同类型的参数将决定代码的复杂程度。如果您只有 0,1 或 2 个参数并且它们始终是对象,请坚持使用 @ 987654342@、invokeSelector:withObject: 和 invokeSelector:withObject:withObject:。
如果答案是“固定的方法签名集”,那么答案就在上面;只需声明一个函数指针,其中包含您想要使用的所有不同可能的方法签名,并在运行时选择正确的方法,然后按照上述方法将其作为函数调用调用。
现在,如果答案是“具有许多不同参数组合的任意选择器集”,则答案要困难得多。您需要使用libffi(或类似的东西)以编程方式执行编译器在编译msgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2); 时所做的事情。 libffi 提供了使用几乎任意参数和返回类型对调用进行编码所需的一切。
它并不容易使用。事实上,使用 libffi 构建自己的堆栈框架已经够难了,编写一个脚本来转储所有可能的调用组合并为每个组合创建一个覆盖函数可能会更容易,可能会将参数作为NSArray* 容器和在内部解码它们。类似(自动生成):
void msgSendVoidStrStr(id obj, SEL _cmd, NSArray*args) {
objc_msgSend(obj, _cmd, [args objectAtIndex:0], [args objectAtIndex:1]);
}
事实证明,这比编写一堆技巧性运行时代码更容易调试。