9.4 id数据类型与静态类型

如果id数据类型可以用来存储任何类型的对象,为什么不把所有的对象都声明为id类型呢?为什么不要养成滥用这种通用数据类型的习惯,有以下的几个原因。

首先,将一个变量定义为特定类的对象时,使用的是静态形态。“静态”这个词指的是这个变量总是用于存储特定类的对象。这样,存储在这种形态中的对象的类是预定的,也就是静态的。使用静态类型时,编译器尽可能确保变量的用法在程序中始终保持一致。编译器能够通过检查来确定应用于对象的方法是由该类定义的或者由该类继承,否则它将显示警告消息。这样,在程序中声明名为myRect的Rectangle变量时,编译器就会检查对myRect调用的每个方法是定义在Rectangle类中或者是从父类继承的。

注意有一些方法可以调用变量指定的方法,在这种的情况下编译器不会进行检查。

但是,如果检查是在运行时执行的,为什么关心静态类型呢?关心静态形态是因为它能更好地在程序编译阶段而不是在运行时指出错误。如果把它留到运行时,你可能在运行程序时没有发现错误。假如程序能够投入应用,也许就会有一些可怜的信任你的使用者发现在他运行程序时特定的对象不能够识别的方法。

使用静态类型的另一个原因是它能够提高程序的可读性。考虑以下声明:


id f1;



Fraction*f1;


你认为哪个声明更容易理解,也就是说,对于读者来讲哪个更能清楚地说明使用f1变量的目的?结合使用静态类型和有意义的变量名(在以前的例子中,我们并没有刻意选择合适的变量名)将会对程序的自说明性产生深远的影响。

动态类型的参数和返回类型

如果使用动态类型来调用一个方法,需要注意以下的规则:如果在多个类中实现名称相同的方法,那么每个方法都必须符合各个参数的类型和返回值类型。这样编译器才能为消息表达式生成正确的代码。

编译器会对它所遇到的每个类声明执行一致性检查。如果有一个或多个方法在参数或者返回类型上存在冲突,编译器就会显示警告消息。例如:Fraction和Complex类中都包含add:方法。然而,Fraction类的参数和返回类型都是Fraction对象,而Complex类的参数和返回类型是Complex对象。如果frac1和myFracrt都是Fraction的对象,而comp1和myComplex都是Complex的对象,那么以下声明


result=[myFract add:frac1];



result=[myComplex add:comp1];


不会导致编译器显示任何警告消息,这是因为,在这两种情况下消息的接收者都是静态类型,并且编译器可以检查这些方法的使用是否和在接收者类中定义的一致。

如果dataValue1和dataValue2是id变量,那么语句


result=[dataValue1 add:dataValue2];


会导致编译器生成代码来将参数传递给add:方法并通过假设处理其返回值。

在运行时,Objective-C运行时系统仍然检查存储在dataValue1中对象所属的确切类,并选择相应的方法来执行。然而,编译器可能生成不正确的代码来向方法传递参数或处理返回值。当一个方法选取对象作为它的参数,而另一个方法选取浮点数作为参数时,很有可能发生这种情况。如果这两个方法之间的不一致性仅在于对象类型的不同(例如,Fraction的add:方法使用Fraction对象作为其参数和返回值,而Complex的add:方法使用Complex对象作为参数),编译器仍然能够生成正确的代码,因为传递给对象的引用是内存地址(即指针)。