2015-10-18 7 views
28

Ich versuche, diese Erweiterung zu machen:Return instanceType in Swift

extension UIViewController 
{ 
    class func initialize(storyboardName: String, storyboardId: String) -> Self 
    { 
     let storyboad = UIStoryboard(name: storyboardName, bundle: nil) 
     let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self 

     return controller 
    } 
} 

Aber ich kompilieren Fehler:

error: cannot convert return expression of type 'UIViewController' to return type 'Self'

Ist es möglich? Auch ich möchte es als init(storyboardName: String, storyboardId: String)

+2

Bitte schreiben Sie keine Antworten in Ihrer Frage. – Rizier123

Antwort

56

ähnlich wie in Using 'self' in class extension functions in Swift, können Sie eine allgemeine Hilfsmethode definieren, die die Art der Selbst vom rufenden Kontext folgert:

extension UIViewController 
{ 
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self 
    { 
     return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId) 
    } 

    private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T 
    { 
     let storyboard = UIStoryboard(name: storyboardName, bundle: nil) 
     let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T 
     return controller 
    } 
} 

Dann

let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id") 

compiliert, und die Art wird als MyViewController gefolgert.


Update für Swift 3:

extension UIViewController 
{ 
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self 
    { 
     return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId) 
    } 

    private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T 
    { 
     let storyboard = UIStoryboard(name: storyboardName, bundle: nil) 
     let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T 
     return controller 
    } 
} 

Eine andere mögliche Lösung, mit unsafeDowncast:

extension UIViewController 
{ 
    class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self 
    { 
     let storyboard = UIStoryboard(name: storyboardName, bundle: nil) 
     let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) 
     return unsafeDowncast(controller, to: self) 
    } 
} 
+9

Hallo Martin, Könnten Sie bitte erklären, wie der Compiler in der Lage ist, den generischen Typ T zu identifizieren und in der Lage zu sein, ihn auf Self zu übertragen? – Adithya

+0

Leute, die "private klasse func" anstatt "static func" sagen, haben viel Stil. :) – Fattie

+0

es ist interessant, dass, wenn Sie es einfach den Basistyp zurückgeben - nicht mit einem generischen zu stören - es funktioniert perfekt bei ** Laufzeit ** (die tatsächliche Unterklasse zurück), aber Sie müssten Ergebnisse in Code, zu "Redaktionszeit". Beispiel: stackoverflow.com/a/42053648/294884 – Fattie

13

Self wird zur Kompilierzeit, nicht zur Laufzeit bestimmt. In Ihrem Code ist Self genau gleich UIViewController, nicht "die Unterklasse, die das zufällig aufruft." Dies wird UIViewController zurückgeben und der Aufrufer wird as es in die richtige Unterklasse haben. Ich nehme an, das ist, was Sie vermeiden wollten (obwohl es die "normale Cocoa" -Methode ist, es zu tun, so ist einfach UIViewController ist wahrscheinlich die beste Lösung).

Hinweis: Sie sollten die Funktion initialize auf keinen Fall benennen. Das ist eine existierende Klassenfunktion von NSObject und würde im besten Fall Verwirrung stiften, im schlimmsten Fall Fehler.

Aber wenn Sie die as des Aufrufers vermeiden möchten, ist Unterklasse normalerweise nicht das Tool, um Funktionalität in Swift hinzuzufügen. Stattdessen möchten Sie normalerweise Generika und Protokolle. In diesem Fall sind Generika alles was Sie brauchen.

func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC { 
    let storyboad = UIStoryboard(name name: storyboardName, bundle: nil) 
    let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC 

    return controller 
} 

Dies ist keine Klassenmethode. Es ist nur eine Funktion. Es gibt keine Notwendigkeit für eine Klasse hier.

let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId) 
+2

Dies ist eine gute Antwort. Ich hatte vor einiger Zeit ein sehr ähnliches Problem. Ich wollte ein Protokoll, das Klassen, die von einem Storyboard instanziiert werden könnten, implementieren könnte, so dass der Aufrufer den exakten Typ des instanziierten Ansicht-Controllers nicht kennen musste. Ich habe diesen Ansatz aufgegeben. Vielleicht probiere ich es nochmal, aber mit einem Generic im Protokoll. – NRitH