你们都可以在集合中找到一个类型的第一个实例,并将该实例作为具体类型返回吗?

Can you both find a first instance of a type in a collection, returning that instance as that concrete type?

这段代码让我很困扰。下面,我试图在 NavigationController 的堆栈中找到特定类型 ViewController 的第一个实例。简单的。但是当我找到它时,我必须将它转换为我刚刚寻找的类型,这对我来说似乎是多余的。

func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {

    guard let foundViewController = viewControllers.first(where: { [=10=] is T }) as? T else {
        return nil
    }

    self.popToViewController(foundViewController, animated:animated)

    return foundViewController
}

我唯一能想到的就是这个...

func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {

    guard let foundViewController = viewControllers.flatMap({ [=11=] as? T }).first() else {
        return nil
    }

    self.popToViewController(foundViewController, animated:animated)

    return foundViewController
}

...但我反复发现像这样使用 flatMap 会使阅读代码的人感到困惑,并且正如下面的评论中正确指出的那样,它会遍历整个集合,而 first 不会那样做。

那么还有其他方法可以解决这个问题吗?

我赞成组合 flatMaplazy 以获得有条件地转换为 T 的行为,去除不匹配,并且 not 枚举整个数组:

func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
    guard let foundViewController = viewControllers.lazy.flatMap({ [=10=] as? T }).first {
        return nil
    }

    self.popToViewController(foundViewController, animated:animated)
    return foundViewController
}

至于 "confusing people that read the code:" flatMap 是相当地道的 Swift,并且与 upcoming rename to compactMap 会更不歧义。如果您的环境中的读者真的有问题,您总是可以编写一个小助手(通用或非通用)以更清晰的名称执行相同的工作。

您可以使用 case patterns 来 select 您感兴趣的类型的 viewController 并且 pop 和 return 第一个你找到一个:

extension UINavigationController {
    func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {

        for case let vc as T in viewControllers {
            self.popToViewController(vc, animated: animated)
            return vc
        }

        return nil
    }
}

示例:

使用堆栈中 OrangeViewController 到 return 到 GreenViewController 中的按钮:

@IBAction func popToGreen(_ sender: UIButton) {
     let greenVC = self.navigationController?.popToFirstViewController(
           ofType: GreenViewController.self,
         animated: true
     )

     // Modify a property in GreenViewController that
     // will be moved into a label in viewWillAppear      
     greenVC?.labelText = "Returned here from Orange"
}

popToLastViewController(ofType:animated:)

您可能还希望函数弹出到最近的 viewController 类型。这很容易通过简单的修改(添加 .reversed())来实现:

func popToLastViewController<T:UIViewController>(ofType type:T.Type, animated: Bool) -> T? {

    for case let vc as T in viewControllers.reversed() {
        self.popToViewController(vc, animated: animated)
        return vc
    }

    return nil
}