简体   繁体   中英

Swift Generics: function with T.Type as parameter returns optional T

I'm attempting to write a series of generic functions that will sort through a stack of viewControllers by passing a UIViewController class or subclass Type and then either return the instance of the "found" viewController or nil. So far I have been unable to even get this simple snippet to compile:

extension UINavigationController {

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

and would call:

navController.fhk_find(fooViewController.self)

However, the compiler is telling me that viewControllerType is not a type.

I'm not exactly sure what I'm missing here...

You need to cast the viewControllers.first (in case it exist) to T , rather than to the parameter viewControllerType . In fact, you won't need to make use of this parameter at all; you can modify your extension to the following:

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

Example usage follows below. Note that you call with fooViewController.dynamicType rather than fooViewController.self . The latter gives you the value of fooViewController.self rather than the type, ie, fooViewController.self = fooViewController.self .

Now note, however, that an attempted conversion from a subclass type to its superclass will always succeed, so the solution above will correctly identify subclass instances (subclasses of UIViewController ), whereas if you query for a superclass instance (ie, T as superclass UIViewController ) then viewController = viewController as? T viewController = viewController as? T will always succeed even if viewController is in fact an instance of a subclass of UIViewController .


Using fhkFindFirst(...) to identify subclass instances: OK

In the following example two subclass instances are correctly identified and their references returned to caller.

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> */

Using fhkFindFirst(...) to find superclass instances: not as intended

Whereas in the following case, fooViewController is an UIViewController object, and is mis-identified as a BarViewController , since type conversion of of BarViewController object (in UINavigationController.viewControllers ) to UIViewController succeeds.

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> */

To wrap up; the above will work as long as you only use the method to search for subclasses of UIViewController ; whereas if you try to search for a superclass object, you will get the first controller in UINavigationController.viewControllers .


Addition with regard to edelaney05:s related question in comments below

I'll start by quoting the question in case the comment were to be deleted:

edelaney05: Is there a way to guard against this? This is a pretty interesting edge case to consider especially if you've got an intermediate class. class FooVC: AwesomeVC { ... } , class BarVC: AwesomeVC { ... } , and class AwesomeVC: UIViewController { ... } .

Yes , you can work around this behaviour, in case you know you'll be working with other than only pure (1st-level) subclasses of UIViewController ; say, to allow for finding first instance of

  • Subclasses to UIViewController . (+)
  • Subclasses to these subclasses. (++)

We can make use of dynamic type comparison to ensure that we don't perform a conversion of a subclass instance to its superclass (and hence misidentifying a subclass instance as the superclass instance we're looking for).

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> */

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM