web-dev-qa-db-fra.com

Renvoyer le type d'instance dans Swift

J'essaie de faire cette extension:

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
    }
}

Mais j'obtiens une erreur de compilation:

erreur: impossible de convertir l'expression de retour de type 'UIViewController' en retour de type 'Self'

C'est possible? Je veux aussi le faire comme init(storyboardName: String, storyboardId: String)

35
ChikabuZ

Similaire à en utilisant 'self' dans les fonctions d'extension de classe dans Swift , vous pouvez définir une méthode d'assistance générique qui déduit le type de self à partir du contexte d'appel:

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
    }
}

Alors

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

compile et le type est déduit comme MyViewController.


Mise à jour pour 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
    }
}

Une autre solution possible, en utilisant 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)
    }
}
65
Martin R

Self est déterminé au moment de la compilation et non à l'exécution. Dans votre code, Self est exactement équivalent à UIViewController, pas "la sous-classe qui se trouve appeler cela". Cela va retourner UIViewController et l'appelant devra le as dans la bonne sous-classe. Je suppose que c'est ce que vous tentiez d'éviter (bien que ce soit la façon "normale de cacao" de le faire, donc renvoyer simplement UIViewController est probablement la meilleure solution).

Remarque: vous ne devez pas nommer la fonction initialize dans tous les cas. C'est une fonction de classe existante de NSObject et causerait de la confusion au mieux, des bogues au pire.

Mais si vous voulez éviter le as de l'appelant, le sous-classement n'est généralement pas l'outil pour ajouter des fonctionnalités dans Swift. Au lieu de cela, vous voulez généralement des génériques et des protocoles. Dans ce cas, les génériques sont tout ce dont vous avez besoin.

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
}

Ce n'est pas une méthode de classe. C'est juste une fonction. Il n'y a pas besoin de cours ici.

let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
15
Rob Napier

Une solution plus propre (au moins visuellement plus nette):

class func initialize(storyboardName: String, storyboardId: String) -> Self {
    // The absurdity that's Swift's type system. If something is possible to do with two functions, why not let it be just one?
    func loadFromImpl<T>() -> T {
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: storyboardId).view as! T
    }
    return loadFromImpl()
}
0
mojuba

Une autre façon consiste à utiliser un protocole, qui vous permet également de renvoyer Self.

protocol StoryboardGeneratable {

}

extension UIViewController: StoryboardGeneratable {

}

extension StoryboardGeneratable where Self: UIViewController
{
    static func initialize(storyboardName: String, storyboardId: String) -> Self
    {
        let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
        return controller
    }
}
0
ukim