通常情况下我们可以对id或者class类型的变量发送任何我们想发送的消息,这也是id类型的真正目的:id是一个可以接受任意Objc类型消息的任意类型。
我们会在很多情况下使用到id类型,但最普通的情况就是类似以下对NSArray的调用:
NSString *desString = [[someArray objectAtIndex:0] substringFromIndex:5];
在上述示例中,我们并没有把objectAtIndex返回的结果强制转换为NSString然后再调用它的substringFromIndex方法,因为我们知道,只要NSArray的第一个object能够响应substringFromIndex方法,那么这个调用就是正确的(优良的架构设计会保证这个Array中存的是NSString类型的数据)。
-------------------------------------
在说这个缺陷之前,需要解释一下"失败的推测"(False assumptions), 这种情况通常发生的原因是编译器不强制要求参数类型信息,所以,编译完毕后的运行,我们只能确定方法调用一定是存在的(由于编译过程中的编译器确定方法签名的方式,可能运行文件中的方法已经不是我们想要的方法了),但不能确定是否是正确的方法。
编译器在编译时候需要知道所调用的方法的明确信息。对于id类型来说,编译器可以不需要知道id类型的实际类型,但它需要知道所有参数的字节长度和显式的返回类型。这是因为对这些参数的整理(出栈入栈)是在编译时确定的。
以上过程对于我们来说都是透明的,参数类型是由编译器在编译时根据你要调用的方法名字,去所包含的头文件中搜索所得到相应的方法,然后读取参数类型。这里的搜索过程只匹配第一个搜索到的方法名字。
---------------------------------------
以上情况在99%的情况下不会出问题,即使你所要调用的方法写的模棱两可,但由于Objc方法命名隐含了数据的类型,所以基本不会出问题。
下面考虑1%的情况。
假设你自定义了一个类名字叫myClass,并且有一个方法名字为currentPoint,返回类型为int。当你试图获得存储在Array中的myClass类的currentPoint值时,以下代码看起来没有错误:
int result = [[someArray objectAtIndex:0] currentPoint];
但实际上,这个方法返回的值永远不是你所期望的,甚至都不是int类型的。
什么地方出错了呢?
方法调用完全没有问题。问题在于,编译器在混合参数类型的时候做了错误的操作,导致了失败的推测,即编译时确定的方法是错误的。
编译器通过方法签名来获取参数(方法签名通过接收者的类型和接收者所有有效方法的方法名字来确定)在确定参数和方法签名之后,编译器才能确定你所要调用的函数。由于本例中我们的方法类型是id,所以编译器需要在所有已知方法列表中查询这个名字的方法。
很不幸的是,本例中,编译器认为NSBezierPath类的currentPath方法是我们想要调用的方法,并且采用了这个currentPath的参数类型和返回类型作为方法签名。NSBezierPath的currentPath方法返回一个结构体而不是int,在运行时,返回的必然不是我们想要的值。
如何解决这个问题?
很简单 在调用之前加一个强制转换。
int result = [(MyClass *)[someArray objectAtIndex:0] currentPoint];
其他问题:
对于实例来说,我们可以通过强制类型转换来解决这个问题。但是如果这2个方法都是类的方法
+[NSBezierPath currentPath ] 和 +[MyClass currentPoint] .我们就不可能通过转换来避免这个问题。在这种情况下,如果不能改代码,我们只有绕过RunTime的调用,自己使用objc_msgSend来调用正确的方法:
int result = objc_msgSend([SomeArray objectAtIndex:0],@selector(currentPoint));
注:在XCode4.2中(ARC默认打开的情况下),如果发生了上述的情况,编译器会报一个错误:
Automatic Reference Counting Issue.
本文探讨Objective-C中id类型的潜在问题,特别是在方法调用时可能出现的“失败的推测”。介绍了如何通过类型转换或使用objc_msgSend来解决由编译器错误推测引起的问题。

523

被折叠的 条评论
为什么被折叠?



