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中不调用异步方法?

Objective C - 让方法只调用一次

将 NSStrings 转换为 C 字符并从 Objective-C 调用 C 函数

Objective C 查看方法被调用次数的最佳方法

我可以使用 phonegap 来调用我的自定义 Objective C 类的方法吗?

在 Objective C 中调用方法时遇到问题(Apple 文档示例)