【问题标题】:How does the compiler determine which messages can be sent to an id type variable?编译器如何确定哪些消息可以发送到 id 类型变量?
【发布时间】:2012-10-25 21:03:33
【问题描述】:

我在 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 的结果?

非常感谢。

【问题讨论】:

  • 实际上,使用objective-c,您可以在运行时向对象添加新方法,因此编译器无论如何都不会意识到这一点。在使用if ([obj respondsToSelector:... 调用对象之前,您应该检查对象是否支持该特定方法。

标签: objective-c ios compiler-construction


【解决方案1】:

或者编译器是否允许在 ARC 之前调用 id 上的任何方法(是否导入),而现在对未导入方法的投诉只是 ARC 的结果?

这是正确的。没有 ARC,这是一个警告。使用 ARC,这是一个错误(因为如果 ARC 在这里猜错了,可能会导致严重的问题)。

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

【讨论】:

  • 这是一个非常有趣的链接!非常感谢!
【解决方案2】:

对象(类型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

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

【讨论】:

  • 好的,谢谢,我明白了。但是在我的测试中,我有一个名为 TestClass 的类,它实现了一个名为“test”的方法。当我尝试使用[[self.tabBarController.viewControllers objectAtIndex:0] test]; 而不导入TestClass.h 时,它不会让我构建它。 (ARC语义问题)。所以我猜这在没有 ARC 的情况下也可以工作,但是由于 ARC 需要了解方法实现来进行正确的内存管理,所以如果不导入 TestClass.h 就会失败。
  • 我的信息可能有点过时了,我在 ARC 方面做得不多。但这就是它在过去的工作方式。 :)
猜你喜欢
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多