Objective C 使用字符串调用方法
Posted
技术标签:
【中文标题】Objective C 使用字符串调用方法【英文标题】:objective C using a string to call a method 【发布时间】:2012-06-22 22:46:12 【问题描述】:嗨,我是目标 C 的新手,想知道是否有人可以帮助我解决这个问题。我有几种不同的方法,每种方法都需要 3 个输入值,通常使用
[self methodA:1 height:10 speed:3]
但是我想从 plist 中的字符串中读取方法名称,例如,如果字符串是 methodB,我会得到 p>
[self methodB:1 height:10 speed:3]
对于“方法C”
[self methodC:1 height:10 speed:3]
等等。
任何想法我可以如何做到这一点我尝试使用 NSSelectorFromString 将字符串定义为选择器
NSString *string = [plistA objectForKey:@"method"];
SEL select = NSSelectorFromString(string);
[self performSelector:select:c height:b speed:a];
但是这不起作用,任何帮助都将不胜感激。 已尝试以下解决方案,但无法在这里工作是我尝试过的。
所以回顾一下,我有诸如
之类的方法 spawnEnemyA:2 withHeight:3 withSpeed:4
spawnEnemyB:3 withHeight:2 withSpeed:5
我想从 plist 文件中读取我想传递给这些方法的值以及方法类型。我的代码如下,//////////////////////////////////// /////////////////
//这些是我从 plist 中读取的值,我希望我的方法使用
int a = [[enemySettings objectForKey:@"speed"] intValue];
int b = [[enemySettings objectForKey:@"position"] intValue];
int c = [[enemySettings objectForKey:@"delay"] intValue];
// I Also read the method name from the plist and combine it into a single string
NSString *method = [enemySettings objectForKey:@"enemytype"];
NSString *label1 = @"spawn";
NSString *label2 = @":withHeight:withSpeed:";
NSString *combined = [NSString stringWithFormat:@"%@%@%@",label1, method,label2];
//Check that the string is correct get spawnEnemyA:withHeight:withSpeed:
CCLOG(@"%@",combined);
//This is the Invocation part
NSInvocation * invocation = [ NSInvocation new ];
[ invocation setSelector: NSSelectorFromString(combined)];
[ invocation setArgument: &c atIndex: 2 ];
[ invocation setArgument: &b atIndex: 3 ];
[ invocation setArgument: &a atIndex: 4 ];
[ invocation invokeWithTarget:self ];
[invocation release ];
/////////////////////////////////////// //////////////////////p>
代码编译没有任何错误,但没有调用方法。有任何想法吗?干杯
【问题讨论】:
【参考方案1】:您不能将 performSelector
用于具有 3 个(或更多)参数的方法。
但为了您的信息,这里是如何使用它:
SEL m1;
SEL m2;
SEL m3;
m1 = NSSelectorFromString( @"someMethodWithoutArg" );
m2 = NSSelectorFromString( @"someMethodWithAnArg:" );
m1 = NSSelectorFromString( @"someMethodWithAnArg:andAnotherOne:" );
[ someObject performSelector: m1 ];
[ someObject performSelector: m2 withObject: anArg ];
[ someObject performSelector: m2 withObject: anArg withObject: anOtherArg ];
对于参数超过 2 个的方法,您必须使用 NSInvocation 类。 查看文档以了解如何使用它。
基本上:
NSInvocation * invocation = [ NSInvocation new ];
[ invocation setSelector: NSStringFromSelector( @"methodWithArg1:arg2:arg3:" ) ];
// Argument 1 is at index 2, as there is self and _cmd before
[ invocation setArgument: &arg1 atIndex: 2 ];
[ invocation setArgument: &arg2 atIndex: 3 ];
[ invocation setArgument: &arg3 atIndex: 4 ];
[ invocation invokeWithTarget: targetObject ];
// If you need to get the return value
[ invocation getReturnValue: &someVar ];
[ invocation release ];
【讨论】:
我明白了,我很喜欢空间。 :) 这也行。对于这样的调用,我更喜欢强制转换的objc_msgSend()
以提高速度,最重要的是,类型安全,因为如果我以后搞砸类型,编译器会抱怨。
我完全同意速度。但是对于初学者来说,我认为 Objective-C 的方式可能不会那么混乱,即使了解运行时的工作原理非常重要。我不同意类型安全,恕我直言,两种方式都是类型不安全的。这就是objc_msgSend
的目的。否则不会是通用的。无论如何,谢谢你的评论。 :)(顺便说一句,是的,我喜欢空间!)
实际上,假设您的转换正确,objc_msgSend()
的类型安全性要高得多。调用的参数实际上是无类型的。那个代码是错误的;您需要传递参数的地址(我没有第一次阅读)!所有这些指针魔术 goop 肯定会增加很多复杂性。
更正了 setArgument 问题,感谢指出。 :) 关于类型安全,你说:«假设演员表是正确的»。这不是真正的类型安全,因为您可以转换为任何类型。这种类型的演员只是告诉编译器闭嘴。关于参数,因为objc_msgSend
原型有...
,你也可以传递一切(也因为objc_msgSend实际上是用汇编实现的,然后不太关心类型)。通过转换函数指针,您可以在示例中添加一些安全性。这很聪明,但我认为它并不比 NSInvocation 更安全......
而且我不知道它是否真的适合这种讨论,即使我肯定会提出这样的论点:D【参考方案2】:
你可以直接使用objc_msgsend
:
NSString *methodName = [plistA objectForKey:@"method"];
objc_msgSend(self, methodName, c, b, a);
注意选择器必须包含所有片段,例如@"method:height:speed:"
【讨论】:
您需要将调用强制转换为 objc_msgSend() 才能正确。 对于初学者来说可能有点低级; ) 另外请记住,如果需要返回值,还有objc_msgSend_fpret
(浮点返回)和objc_msgSend_stret
(结构返回)。
是的,我可以想象它可能有点低级,但至少你可以了解 Obj-C 是如何构建在 C 上的 :) 在这里查看有关此方法的更多详细信息:@987654321 @【参考方案3】:
一般来说,这种动态通常表示一种反模式。以这种方式将数据与实现相结合通常不是最佳做法。
但有时,这是必要的。如果您要走这条路,那么鉴于您的各种方法声明可能如下所示:
- (void)methodAWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;
- (void)methodBWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;
- (void)methodCWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;
你可能想要这样的东西:
NSString *selName = [NSString stringWithFormat:@"method%@Width:height:speed:", ... one of @"A", @"B", or @"C" ....];
SEL selector = NSelectorFromString(selName);
然后:
if (![target respondsToSelector:selector])
return; // no can do
void (*castMsgSend)(id, SEL, NSUInteger, NSUInteger, NSUInteger) = (void*)objc_msgSend;
castMsgSend(target, selector, 1, 10, 3);
每个方法调用都被编译为对objc_msgSend()
的调用。通过执行上述操作,您将创建一个完全类型安全/类型检查的调用站点,该站点通过正常的 Objective-C 消息传递机制,但选择器是动态定义的。\
虽然performSelector:
(和多参数变体)很方便,但它们不能处理非对象类型。
而且,正如 MacMade 在评论中指出的那样,请注意浮点和结构返回。它们使用编译器在正常的[foo bar]
情况下自动处理的 objc_msgSend() 的不同变体。
【讨论】:
当我尝试这个时,我收到以下关于 void (castMSgSend) 部分的错误消息 无法使用右值初始化 'void()(id, SEL, NSUInteger, NSUInteger,NSUInteger)' 类型的变量'void *' 类型的我该如何解决这个问题?干杯@bbum【参考方案4】:您应该将下面的行替换为:
[self performSelector:select:c height:b speed:a];
并写下:
[self performSelector:select withObject:[NSArray arrayWithObjects:c,b,a,nil]];
【讨论】:
以上是关于Objective C 使用字符串调用方法的主要内容,如果未能解决你的问题,请参考以下文章
如何使用OCMock验证在Objective C中不调用异步方法?
将 NSStrings 转换为 C 字符并从 Objective-C 调用 C 函数