【问题标题】:Swift Generics: function with T.Type as parameter returns optional TSwift 泛型:以 T.Type 作为参数的函数返回可选的 T
【发布时间】:2016-01-29 16:22:41
【问题描述】:

我正在尝试编写一系列通用函数,这些函数将通过传递UIViewController 类或子类类型对一堆 viewController 进行排序,然后返回“找到的”viewController 的实例或返回 nil。到目前为止,我什至无法编译这个简单的 sn-p:

extension UINavigationController {

    func fhk_find<T: UIViewController>(viewControllerType: T.Type) -> T?
    {
        if let viewController = viewControllers.first as? viewControllerType {
            return viewController
        }
        else {
            return nil
        }
    }
}

并且会打电话:

navController.fhk_find(fooViewController.self)

但是,编译器告诉我 viewControllerType 不是类型。

我不确定我在这里缺少什么......

【问题讨论】:

    标签: ios swift generics


    【解决方案1】:

    您需要将viewControllers.first(如果存在)转换为T,而不是参数viewControllerType。事实上,您根本不需要使用这个参数;您可以将您的扩展修改为以下内容:

    extension UINavigationController {
        func fhkFindFirst<T: UIViewController>(_: T.Type) -> T? {
            for viewController in viewControllers {
                if let viewController = viewController as? T {
                    return viewController
                }
            }
            return nil
        }
    }
    

    示例用法如下。请注意,您使用fooViewController.dynamicType 而不是fooViewController.self 调用。后者为您提供fooViewController.self,而不是类型,即fooViewController.self = fooViewController.self

    现在请注意,尝试从子类类型转换为其超类将始终成功,因此上述解决方案将正确识别子类实例(UIViewController 的子类),而如果您查询超类实例(即, T 作为超类 UIViewController) 然后 viewController = viewController as? T 将始终成功,即使 viewController 实际上是 UIViewController 的子类的实例。


    使用fhkFindFirst(...) 识别子类实例:OK

    在以下示例中,两个子类实例被正确识别,并将它们的引用返回给调用者。

    class FooViewController : UIViewController { }
    class BarViewController : UIViewController { }
    
    let fooViewController = FooViewController()
    let barViewController = BarViewController()
    
    let navController = UINavigationController(rootViewController: fooViewController)
    navController.addChildViewController(barViewController)
    
    print(navController.fhkFindFirst(fooViewController.dynamicType) ?? "None found.")
    // or: print(navController.fhkFindFirst(FooViewController))
    /* <__lldb_expr_1582.FooViewController: 0x7fb97840ad80> */
    
    print(navController.fhkFindFirst(barViewController.dynamicType) ?? "None found.")
    // or: print(navController.fhkFindFirst(BarViewController))
    /* <__lldb_expr_1582.BarViewController: 0x7fb978709340> */
    

    使用fhkFindFirst(...) 查找超类实例:不符合预期

    而在以下情况下,fooViewController 是一个UIViewController 对象,并且被错误识别为BarViewController,因为BarViewController 对象(在UINavigationController.viewControllers 中)到UIViewController 的类型转换成功.

    class BarViewController : UIViewController { }
    
    let fooViewController = UIViewController()
    let barViewController = BarViewController()
    
    let navController = UINavigationController(rootViewController: barViewController)
    navController.addChildViewController(fooViewController)
    
    print(navController.fhkFindFirst(fooViewController.dynamicType) ?? "None found.")
    // or: print(navController.fhkFindFirst(UIViewController))
        /* <__lldb_expr_1533.BarViewController: 0x7fa519e2bd40> <-- "wrong" one */
    
    print(navController.fhkFindFirst(barViewController.dynamicType) ?? "None found.")
    // or: print(navController.fhkFindFirst(BarViewController))
        /* <__lldb_expr_1533.BarViewController: 0x7fa519e2bd40> */
    

    总结;只要您只使用该方法搜索UIViewController的子类,上述方法就可以使用;而如果您尝试搜索超类对象,您将在UINavigationController.viewControllers 中获得第一个控制器。


    关于 edelaney05:s 下面 cmets 中的相关问题的补充

    如果评论被删除,我将首先引用问题:

    edelaney05:有没有办法防止这种情况发生?这是一个非常有趣的边缘案例,特别是如果你有一个 中级。 class FooVC: AwesomeVC { ... }class BarVC: AwesomeVC { ... }class AwesomeVC: UIViewController { ... }

    是的,您可以解决此问题,以防您知道您将使用 UIViewController 的纯(1 级)子类以外的其他子类;比如说,允许找到第一个实例

    • UIViewController 的子类。 (+)
    • 这些子类的子类。 (++)

    我们可以利用动态类型比较来确保我们不会将子类实例转换为其超类(从而将子类实例错误地识别为我们正在寻找的超类实例)。

    extension UINavigationController {
        func fhkFindFirst<T: UIViewController>(_: T.Type) -> T? {
            for viewController in viewControllers {
                if let supClassType = viewController.superclass?.dynamicType where supClassType != T.Type.self {
                    if let viewController = viewController as? T {
                        return viewController
                    }
                }
            }
            return nil
        }
    }
    
    class FooBarViewController: UIViewController { }
    
    class FooViewController : FooBarViewController { }
    class BarViewController : FooBarViewController { }
    
    let fooBarViewController = FooBarViewController()
    let fooViewController = FooViewController()
    let barViewController = BarViewController()
    
    let navController = UINavigationController(rootViewController: fooViewController)
    navController.addChildViewController(barViewController)
    navController.addChildViewController(fooBarViewController)
    
    print(navController.fhkFindFirst(FooViewController) ?? "None found.")
    /* <__lldb_expr_1582.FooViewController: 0x7fe22a712e40> */
    
    print(navController.fhkFindFirst(BarViewController) ?? "None found.")
    /* <__lldb_expr_1582.BarViewController: 0x7fe22a4196a0> */
    
    print(navController.fhkFindFirst(FooBarViewController) ?? "None found.")
    /* <__lldb_expr_1582.FooBarViewController: 0x7fe22a70ee60> */
    

    【讨论】:

    • 啊!我知道这会很简单。谢谢!感谢您通过指出 .self 与 .dynamicType 为我省去了未来的头痛。
    • @StephenNewton 乐于助人。另请注意,我更新了您的扩展方法以遵循 Swift camelCase 方法名称的命名约定。
    • @StephenNewton 请注意我的编辑:只要您只搜索 UIViewController 的子类实例,上述解决方案就可以正常工作,但如果您尝试搜索超类对象 (UIViewController)。但是,这应该没问题,因为所有视图控制器都应该是 UIViewController 的子类的实例。
    • @dfri 有没有办法防止这种情况发生?这是一个非常有趣的边缘案例,特别是如果你有一个中级课程。 class FooVC: AwesomeVC { ... }class BarVC: AwesomeVC { ... }class AwesomeVC: UIViewController { ... }
    • @edelaney05 是的,我们可以解决这个问题;请参阅上面的编辑答案。
    猜你喜欢
    • 2016-07-02
    • 2020-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-13
    相关资源
    最近更新 更多