为什么在 Swift 中的 instantiateViewController(withIdentifier:) 之后需要向下转型

Why is downcasting required after instantiateViewController(withIdentifier:) in Swift

为什么我们需要在使用 UIStoryboard 的 instantiateViewController(withIdentifier:) 方法实例化视图控制器之后使用关键字进行向下转型,以便访问视图控制器的属性? UIStoryboard 方法 instantiateViewController(withIdentifier:) 已经 returns 一个 UIViewController 并且根据 故事板 ID 知道哪个 class 或者这就是我假设发生的但不完全正确。

以下代码可以运行并编译,但我想了解原因。如果我根据文档构建它,我不会假设向下转换是必要的,所以我试图找出关于从返回的类型 and/or 对象方面我还没有学习或理解的部分函数。

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)

    }

因为storyboard.instantiateViewController...总是returns一个UIViewController(你的子class的基础class)因此无法知道你的子[=26]的具体实现细节=].

上面提到的方法不会根据情节提要 ID 推断出您的子 class 类型,这是您在向下转换时在代码中所做的事情(参见 here)。

func instantiateViewController(withIdentifier identifier: String) -> UIViewController

所以它有效,因为你从上面的方法得到一个 UIViewController,然后你强制将它向下转换为你的 ItemListViewController(它总是有效,因为你将 ItemListViewController 定义为一个 UIViewController 子class).

PS。不过我不确定我是否理解你的问题,这看起来很简单。

knows which class based on the Storyboard ID

这是完全错误的。 Storyboard ID 是一个字符串。在您的情况下,它恰好是一个静态字符串,但此方法不需要。它可以在 运行 时间计算(我个人编写了这样做的代码)。您的字符串恰好与 class 名称匹配,但不要求必须如此。标识符可以是任何字符串。故事板不是编译过程的一部分。在编译代码和 运行 之间可以很容易地更改情节提要,以便所讨论的对象具有不同的类型。

由于在编译时没有足够的信息来计算 class,编译器要求您明确承诺它会成功,并决定在失败时如何处理。由于您使用 as!,所以您说的是 "please crash if this turns out to be wrong."

您可能需要阅读一些 object 面向编程的背景知识,以帮助您理解。关键概念是 class、instance/object、继承和多态性。

storyboard.instantiateViewController() 将创建 ItemListViewController 的实例,但它作为 UIViewController 返回。如果这部分很难理解那么这就是你需要 objected 导向知识背景的地方。 在 OO 语言中,例如 C++,class(也称为 object)的实例可以通过指向其 parent(或 grandparent)的指针来引用或曾祖父 parent 等)class。在大多数关于 object 方向的文献和教程中,关于继承和转换以及多态性的概念总是使用指向基 classes 和派生 objects 等的指针来说明。 然而,对于 Swift,指针不会像许多其他 OO 语言那样公开。

在你的代码中,并以简化的方式解释这一点,好像它是 C++ 或类似的 OO 语言,因为大多数 OO 教程都是这样解释的:

let viewController = storyboard.instantiateViewController(
            withIdentifier: "ItemListViewController")

viewController 是 class 类型 UIViewController 的 "pointer",它指向 ItemListViewController.[=20 类型的 object =]

编译器将指针 viewController 视为 UIViewController 类型,因此它不知道 ItemListViewController 的任何特定方法或属性,除非您显式进行强制转换以便它了解它们。

这部分是由于多态性。以下面的代码为例:

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 参数的值,您将获得 PersonStudent

现在如果你将 2 传递给函数,那么你可以确定 createRandomPerson 将 return 一个 Student 实例,但你仍然需要向下转换因为在某些情况下 createRandomPerson 将 return 基础实例 class.

与故事板类似,您知道如果您传递正确的标识符,您将获得您期望的视图控制器,但是由于故事板几乎可以创建 UIViewController subclass 的任何实例es(甚至 UIViewController)讨论中的函数将 return 类型设置为 UIViewController

同样,如果您的数学计算错误 - 即您将 1 传递给 createRandomPerson(),或者将错误的标识符传递给故事板,那么您将不会收到您期望的结果。