编译器如何确定哪些消息可以发送到id类型变量?

时间:2012-10-25 21:03:33

标签: objective-c ios compiler-construction

在Xcode中使用TabBarController模板尝试一些事情时,我遇到了这个'问题'(这不是一个问题,我只是感到惊讶,这是可能的)。如果您使用没有故事板的模板,基本设置如下所示:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];

self.tabBarController.viewControllers = @[viewController1, viewController2];

self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

tabBarController的viewControllers属性是NSArray。因此[self.tabBarController objectAtIndex:0]会返回id

所以我总是想如果我想调用我在f.e.声明的方法。在FirstViewController类中,我必须这样做:

FirstViewController *firstVC = (FirstViewController *)[self.tabBarController objectAtIndex:0];
[firstVC someMethod];

但事实证明,这是不必要的,编译器也会让我做以下事情 - 因为我导入了一个声明someMethod的头文件(当然它不一定会增加可读性,但无论如何):

[[self.tabBarController objectAtIndex:0] someMethod];

我根本没想到这一点。所以我假设编译器允许调用id上的任何方法,只要该方法在当前类的范围内的任何类中声明(我的意思是,它的头文件被导入到当前类中) 。如果没有导入声明someMethod的类,编译器将抛出一个错误(但我必须补充一点,我在使用ARC时测试了它。很可能,编译器不会抱怨调用'不使用ARC时id上的未导入的方法... ...

这个假设是否正确?如果可能,您可以提供更多关于ID类型的信息或参考吗?

或者编译器允许在ARC之前调用id(导入与否)上的任何方法,现在对于无导入方法的抱怨只是ARC的结果?

很多。

2 个答案:

答案 0 :(得分:1)

对象(类型id通常是任何继承自NSObject的对象的实例)都有方法。对象要么响应方法签名,要么不响应。

XCode可能会向您发出警告,通过类型检查不会响应该方法,但代码仍会运行。如果对不支持它的对象执行方法,则会出现运行时异常。您可以像任何其他类型的完全不兼容的对象一样转换任何对象,并且仍然在运行时调用该对象的方法。

// This should work at runtime, but generate warning when compiled
// Don't do this, obviously, but it should "work"
NSNumber *array = [NSArray arrayWithObject:@"foobar"];
NSLog([array objectAtIndex:0]); // "foobar"

类型检查实例和它们上面调用的方法在编译时对程序员有好处,但对运行时编译的应用程序没那么多。

所以回答你的问题:

  

编译器如何确定哪些消息可以发送到对象?

它在运行时询问对象。

[myObj myMethod:123];

在ObjC运行时中触发类似这样的伪代码:

if myObj responds to the message with signature "myMethod:"?
  send myObj the "myMethod:" message with arguments [123]
else
  throw an exception

变量声明的类型在运行时根本不重要,因为变量只是指向对象的指针。确定该对象是否响应某种方法不是在编译时完成的。

答案 1 :(得分:1)

  

或者编译器是否允许在ARC之前调用id(导入或不导入)上的任何方法,现在对于无导入方法的抱怨只是ARC的结果?

这是对的。没有ARC这是一个警告。使用ARC,这是一个错误(因为如果猜测错误,ARC可能会遇到严重的问题)。

在某些情况下,此行为可能会导致一些非常微妙的错误,无论是否有ARC。如果有多个方法签名与选择器匹配,则编译器可能会选择错误的返回类型,这会导致非常令人惊讶的运行时行为。 Matt Gallagher在"A big weakness in Objective-C's weak typing."中提供了一个非常好的解释。我遇到了他描述的相同的错误,这是ObjC开发人员应该注意的事情,即使它经常没有出现。