我在 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 的结果?
非常感谢。
或者编译器是否允许在 ARC 之前调用 id 上的任何方法(导入或未导入),而现在对未导入方法的抱怨只是 ARC 的结果?
这是正确的。如果没有 ARC,这就是一个警告。对于 ARC,这是一个错误(因为如果 ARC 在这里猜测错误,它可能会遇到严重的问题)。
在某些情况下,无论有或没有 ARC,此行为都可能导致一些非常微妙的错误。如果有多个方法签名与选择器匹配,则编译器可能会选择错误的返回类型,这可能会导致非常令人惊讶的运行时行为。 Matt Gallagher 在 “Objective-C 弱类型的一大弱点”中对此提供了非常好的解释。 我遇到了他所描述的相同错误,这是 ObjC 开发人员应该意识到的事情,即使它没有出现经常这样。
对象(类型
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
变量声明的类型实际上在运行时根本不重要,因为变量只是指向对象的指针。确定该对象是否响应某个方法并不是在编译时完成的。