2016-01-29 2 views
6

Ich versuche, eine Reihe von generischen Funktionen zu schreiben, die durch einen Stapel von ViewControllern sortieren, indem sie eine UIViewController Klasse oder Unterklasse Type übergeben und dann die Instanz von der "gefundene" viewController oder nil. Bisher hat ich nicht in der Lage gewesen, auch diesen einfachen Code-Schnipsel zu kompilieren:Swift Generics: Funktion mit T.Type als Parameter gibt optional zurück T

extension UINavigationController { 

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

und nennen würde:

navController.fhk_find(fooViewController.self) 

jedoch der Compiler sagt mir, dass viewControllerType kein Typ ist.

ich nicht ganz sicher bin, was ich bin fehlt hier ...

Antwort

4

Sie benötigen viewControllerType die viewControllers.first (falls es existiert) zu T, anstatt auf den Parameter zu werfen. In der Tat müssen Sie diesen Parameter überhaupt nicht verwenden; Sie können Ihre Erweiterung wie folgt ändern:

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

Beispiel Verwendung folgt unten. Beachten Sie, dass Sie mit fooViewController.dynamicType statt fooViewController.self anrufen. Letzteres gibt Ihnen den Wert von fooViewController.self anstelle des Typs, d. H. fooViewController.self = fooViewController.self.

Beachten Sie jedoch, dass eine versuchte Konvertierung von einem Unterklasse-Typ in die Superklasse immer erfolgreich ist. Die obige Lösung identifiziert also Unterklassen-Instanzen (Unterklassen von UIViewController) korrekt, während Sie nach einer Superklasseninstanz suchen. T als Superklasse UIViewController) dann viewController = viewController as? T wird immer erfolgreich sein, auch wenn viewController in der Tat eine Instanz einer Unterklasse von UIViewController ist.


fhkFindFirst(...) Verwendung Unterklasse Instanzen zu identifizieren: OK

Im folgenden Beispiel werden zwei Unterklasse Instanzen korrekt identifiziert und ihre Referenzen zurückgegeben Anrufer.

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(...) Mit Super Instanzen zu finden: nicht als

bestimmt Während im folgenden Fall fooViewController ist ein UIViewController Objekt, und ist als BarViewController-mis identifiziert, da Typumwandlung von BarViewController Objekt (in UINavigationController.viewControllers) bis UIViewController gelingt.

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

Zum Abschluss; das obige funktioniert, solange Sie nur die Methode verwenden, um nach Unterklassen von UIViewController zu suchen; Wenn Sie dagegen nach einem Objekt der Superklasse suchen, erhalten Sie den ersten Controller unter UINavigationController.viewControllers.


Addition in Bezug auf edelaney05: s relevante Frage in den Kommentaren unten

ich unter Angabe die Frage für den Fall beginnen würde der Kommentar gelöscht werden sollte:

edelaney05 : Gibt es eine Möglichkeit, sich davor zu schützen? Dies ist ein ziemlich interessanter Randfall, der besonders dann in Betracht gezogen werden sollte, wenn Sie eine Zwischenklasse haben. class FooVC: AwesomeVC { ... }, class BarVC: AwesomeVC { ... } und class AwesomeVC: UIViewController { ... }.

Ja, können Sie dieses Verhalten umgehen, falls Sie wissen, dass Sie mit anderen arbeiten als nur reine (1. Ebene) Subklassen von UIViewController; sagen, um die erste Instanz von

  • Subklassen zu UIViewController zu ermöglichen. (+)
  • Unterklassen zu diesen Unterklassen. (++)

Wir Verwendung von dynamischem Typ Vergleich machen können, um sicherzustellen, dass wir (keine Umwandlung einer Unterklasse Instanz in der übergeordneten Klasse durchführen und damit eine Unterklasse-Instanz als übergeordnete Klasse Instanz misidentifying wir suchen zum).

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

Ah! Ich wusste, es wäre etwas Einfaches. Vielen Dank! Und danke, dass Sie mir die zukünftigen Kopfschmerzen ersparen, indem Sie auf .self vs .dynamicType hingewiesen haben. –

+0

@StephenNewton Glücklich zu helfen. Beachten Sie auch, dass ich Ihre Erweiterungsmethode aktualisiert habe, um der Namenskonvention Swift 'camelCase' für Methodennamen zu folgen. – dfri

+2

@StephenNewton Beachten Sie mein Edit: Die obige Lösung funktioniert gut, solange Sie nur nach Subklasseninstanzen von 'UIViewController' suchen, aber immer die erste Instanz in' UINavigationController.viewControllers' zurückgeben, wenn Sie nach einem Superklassenobjekt suchen ('UIViewController '). Dies sollte jedoch in Ordnung sein, da alle View-Controller Instanzen von Unterklassen von 'UIViewController' sein sollten. – dfri