【问题标题】:objective C using a string to call a methodObjective C 使用字符串调用方法
【发布时间】: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>

代码编译没有任何错误,但没有调用方法。有任何想法吗?干杯

【问题讨论】:

    标签: objective-c string methods plist


    【解决方案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]];
    

    【讨论】:

      猜你喜欢
      • 2011-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 2013-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多