为什么在使用UIStoryboard的instantiateViewController(withIdentifier:)
方法实例化视图控制器之后,我们需要使用关键字进行向下转换才能访问视图控制器的属性? UIStoryboard方法instantiateViewController(withIdentifier:)
已经返回UIViewController
并根据故事板ID 知道哪个类,或者这是我认为发生但不完全正确的。
以下代码可以工作和编译,但我想了解原因。如果我基于文档构建这个,我不会假设向下转换是必要的,所以我试图找出关于从函数返回的类型和/或对象我没有学习或理解的部分。
func test_TableViewIsNotNilOnViewDidLoad() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
let sut = viewController as! ItemListViewController
_ = sut.view
XCTAssertNotNil(sut.tableView)
}
答案 0 :(得分:3)
因为storyboard.instantiateViewController...
总是返回一个UIViewController(特定子类的基类),因此无法知道特定于子类的实现细节。
上述方法不会根据故事板ID推断您的子类类型,这是您在向下转换时在代码中执行的操作(请参阅here)。
func instantiateViewController(withIdentifier identifier: String) -> UIViewController
所以它的工作原理是因为您从上面的方法中获得了UIViewController
,然后您将其向下转移到ItemListViewController
(它始终有效,因为您将ItemListViewController
定义为UIViewController
} subclass)。
PS。我不确定我是否理解了你的问题,这看起来非常简单。
答案 1 :(得分:1)
根据故事板ID
知道哪个类
这是完全错误的。故事板ID是一个字符串。在您的情况下,它恰好是一个静态字符串,但此方法不需要。它可以在运行时计算(我个人编写的代码就是这样)。您的字符串恰好与类名匹配,但并不要求这是真的。标识符可以是任何字符串。故事板不是编译过程的一部分。故事板可以在编译代码的时间和运行时间之间轻松更改,以便相关对象具有不同的类型。
由于在编译时没有足够的信息来计算类,编译器要求您明确承诺它将解决问题,并决定如果失败则该怎么做。由于您使用的是as!
,因此您说“如果结果出错,请崩溃。”
答案 2 :(得分:1)
您可能需要对面向对象编程进行一些背景阅读,以帮助您理解。关键概念是类,实例/对象,继承和多态。
ItemListViewController
将创建UIViewController
的实例,但它将以let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
的形式返回。如果这部分难以理解,那么这就是您需要有针对性的知识背景的地方。
在OO语言中,例如C ++,类的实例(也称为对象)可以通过指向其父(或祖父母或大祖父等)类的指针来引用。在大多数关于面向对象的文献和教程中,关于继承和转换以及多态的概念总是使用指向基类和派生对象的指针等来说明。
但是对于Swift,指针不会像许多其他OO语言那样暴露出来。
在您的代码中,并以简化的方式解释这一点,并且就像它是C ++或类似的OO语言一样,OO上的大多数教程都解释了这些:
viewController
UIViewController
是类类型ItemListViewController
的“指针”,它指向viewController
类型的对象。
编译器将指针UIViewController
视为类型ItemListViewController
,因此它不知道{{1}}的任何特定方法或属性,除非您明确地执行转换,以便它知道他们。
答案 3 :(得分:1)
这部分是由于多态性造成的。以下面的代码为例:
class Person {
let name: String
}
class Student: Person {
let schoolName: String
}
func createRandomPerson(tag: Int) -> Person {
if tag == 1 { return Person() }
else { return Student() }
}
let p = createRandomPerson(2)
根据tag
参数的值,您可以获得Person
或Student
。
现在,如果您将2
传递给该函数,那么您可以确定createRandomPerson
将返回Student
个实例,但您仍然需要向下转换,因为有些情况{ {1}}将返回基类的实例。
与故事板类似,您知道如果传递正确的标识符,您将获得您期望的视图控制器,但是因为故事板几乎可以创建任何createRandomPerson
子类的实例(甚至{{1} })讨论中的函数将返回类型设置为UIViewController
。
同样地,如果你的数学错误 - 即你将UIViewController
传递给UIViewController
,或将错误的标识符传递给故事板,那么你将无法收到你期望的数据。