代码之家  ›  专栏  ›  技术社区  ›  Piotr Czapla

ObjC内部构件。为什么我的鸭子打字尝试失败了?

  •  2
  • Piotr Czapla  · 技术社区  · 16 年前

    我试着用 id 在objective-c中创建duck类型。这个概念在理论上看起来不错,但在实践中失败了。我无法在方法中使用任何参数。调用了方法,但参数错误。我对对象和原语的随机值的访问性很差。下面我附上一个简单的例子。

    有人知道为什么方法参数是错误的吗?

    注:我对细节感兴趣。我知道如何使下面的例子起作用。

    举个例子: Test 使用属性传递给其他类的 id test

    @implementation Test
    - (void) aSampleMethodWithFloat:(float) f andInt: (int) i {
        NSLog(@"Parameters: %f, %i\n", f, i);
    }
    @end
    

    然后在类中执行以下循环:

    for (int i=0; i < 10; ++i) {
        float f=i*0.1f;
        [tst aSampleMethodWithFloat:f andInt:i]; // warning no method found.
    }
    

    这是我得到的结果。如您所见,方法被调用,但参数是错误的。

    Parameters: 0.000000, 0
    Parameters: -0.000000, 1069128089
    Parameters: -0.000000, 1070176665
    Parameters: 2.000000, 1070805811
    Parameters: -0.000000, 1071225241
    Parameters: 0.000000, 1071644672
    Parameters: 2.000000, 1071854387
    Parameters: 36893488147419103232.000000, 1072064102
    Parameters: -0.000000, 1072273817
    Parameters: -36893488147419103232.000000, 1072483532
    

    我无意中发现当我加上 aSampleMethodWith... 去上课 for 循环警告消失,测试类上的方法被正确调用。

    更新2: 正如JeremyP所指出的,问题的直接原因是浮点数被视为双倍数。但有人知道为什么吗(遵循5why原则:))。

    根据@eman,调用被转换为简单的C函数调用和编译器指令,以获得 SEL . 所以@选择器被搞糊涂了。但为什么呢?编译器在第一个方法调用中拥有所有必需的类型信息。有人知道Objective-C内部信息的好来源吗?我搜索了Objective-C编程语言,但没有找到答案。

    5 回复  |  直到 16 年前
        1
  •  2
  •   JeremyP    16 年前

    [tst aSampleMethodWithFloat:f andInt:i]; 发生时,它应该只传递一个float,因此它将f提升为double。这意味着,在方法中,当编译器 知道它是在处理一个float,f是由传递给方法的前4个字节构成的float,i是由传递给方法的后4个字节构成的int。

    你可以通过以下两种方法来解决这个问题

    注意:在C中使用浮点数时,除了少量的空间外,没有任何增益。你还不如到处用双倍的。

        2
  •  1
  •   shosti    16 年前

    我认为杰里米普关于双打和浮动的问题是正确的。至于实现细节,Objective-C中的消息调度使用 objc_msgSend(id theReceiver, SEL theSelector, ..) C函数(有关某些深层次的细节,请参见 here

    SEL theSelector = @selector(aSampleMethodWithFloat:andInt:);
    objc_msgSend(self.test, theSelector, 1.5f, 5);
    

    SEL 只是一个对应于函数的数字(根据方法签名动态确定)。 objc_msgSend 然后查找方法的实际函数指针(IMP类型)并调用它。自 具有可变数量的参数,它将只使用您传入的数量。如果你要做:

    objc_msgSend(self.test, theSelector, 1.5f);
    

    它将正确地使用1.5f,并将垃圾作为另一个变量。由于方法签名通常表示参数的数量,所以在正常使用下很难做到这一点。

        3
  •  0
  •   neoneye    16 年前

    您可以通过这样的分类来消除警告:

    @interface NSObject (MyTestCategory)
    - (void) aSampleMethodWithFloat:(float) f andInt: (int) i;
    @end
    
        4
  •  0
  •   Jakob Borg    16 年前

    如果调用点没有可用的签名,就不知道参数应该是什么类型的。未定义的方法将假定为 ... 作为参数,这不是你的。如果编译器此时看到任何接口,其中存在有问题的方法,则将使用该定义。

        5
  •  0
  •   Chuck    16 年前