你可能想读这个。您实际上要问的是“我想制作一个新的调度程序”,要回答这个问题,您应该对现有调度程序的工作方式有一个透彻的了解。
请告诉遇到你在做什么?在语言之间架起一座桥梁?因为如果不是这样,那么您将陷入一个非常有趣的兔子洞,但可能不是一个非常有效或优雅的解决方案。
现在:
问题是,我想通过简单地应用类名和方法 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 搜索算法进行逆向工程(不太硬——只是一堆字符串操作和查找),然后传递任意参数,如下所示。
请注意,您的两次调用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()
or (不幸的是,由于多年来没有纪律的程序员sel_getUid()
,它实际上最终被调用)。sel_registerName()
至少你的意图是正确的。
现在,要objc_msgSend()
按照您的意愿使用,您需要回答一个问题,而结果的答案将截然不同。一个答案是“哦,做X”的简单路线,另一个答案是“哦,天哪,你正走在一条痛苦的道路上”。
问题:您是否有一组固定的方法签名,或者您是否必须传递任意一组多种类型的参数?
最终,有多少和多少不同类型的参数将决定代码的复杂程度。 如果您只有 0,1 或 2 个参数并且它们始终是对象,请坚持使用invokeSelector:
和。invokeSelector:withObject:
invokeSelector:withObject:withObject:
如果答案是“固定的方法签名集”,那么答案就在上面;只需声明一个函数指针,其中包含您要使用的所有不同可能的方法签名,并在运行时选择正确的函数指针,然后按照上述方法将其作为函数调用调用。
现在,如果答案是“具有许多不同参数组合的任意选择器集”,那么答案就困难得多。您需要使用libffi(或类似的东西)以编程方式执行编译器在 compile 时所做的事情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]);
}
事实证明,这比编写一堆技巧运行时代码更容易调试。