Swift : Retourner différent type avec une seule méthode

HorusHorus Membre

Salut,


 


Je cherche à  écrire une méthode qui peut retourner différent type en fonction d'un énum.


 


Model : 



//MARK: - MODEL -
class ModelA: NSObject, Model{
var name: String
init(name: String) {
self.name = name
super.init()
}

func build()-> ModelA{
return self
}
}

class ModelB: NSObject, Model{

var pseudo: String
init(pseudo: String) {
self.pseudo = pseudo
super.init()
}

func build()-> ModelB{
return self
}
}


Protocol : 



protocol Model{
associatedtype T
func build()-> T
}

Enum :



enum Api{

case a
case b

func request()->Model{
switch self {
case .a: return ModelA(name: "Test")
case .b: return ModelB(pseudo: "Ok")
}
}
}

Du coup je récupérer une erreur : 


 



 


 


Playground execution failed: error: MyPlayground.playground:52:21: error: protocol 'Model' can only be used as a generic constraint because it has Self or associated type requirements
func request()->Model{

 


 


Je bloque un peu la ... 


Merci d'avance :D


Réponses

  • CéroceCéroce Membre, Modérateur
    mai 2017 modifié #2
    On peut associer des données à  chaque clause d'une énumération:
     
    enum Api {
    case a(ModelA)
    case b(ModelB)
    }
    Ensuite pour construire l'instance de l'enum:
    Api.a(ModelA(name: "Test"))
  • HorusHorus Membre
    mai 2017 modifié #3

    L'idée serait d'avoir un enum (ayant pour 'case' les endpoints de l'API), qui me retourne un object en fonction de la demande 


     


    exemple :


     


    - case getUsers, getUser(id: Int),getProfile(id: Int) ... 


    - request() : Requêter l'API


    - build() : Convertir le JSON en Object


     


    Api.getUsers.request().build() me renvoi : [User]


    Api.getUser(id: 9).request().build() me renvoi : User


    Api.getProfile(id: 1).request().build() me renvoi : Profile etc etc 


  • CéroceCéroce Membre, Modérateur
    mai 2017 modifié #4
    Alors ce sont tes méthodes getUsers(), getUser() et getProfile() qui doivent renvoyer des objets/struct/enum de classes différentes.

    Et toutes ces classes doivent posséder une méthode request(). Tu l'assures en les conformant toutes à  un protocole, ou en les faisant toutes hériter de la même classe parente.

    Mais finalement, je trouve tout ton concept étrange. Tu devrais plutôt avoir
    Api.getUsers { users in
    // Faire des choses utiles
    }
    Parce que:
    1) le code pour lancer la requête HTTP est asynchrone, donc une closure devra être appelée.
    2) le client de l'API n'a pas à  savoir qu'il faut construire la requête et décoder le JSON.
  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #5
    Il te faut une classe générique. Je fais actuellement les courses. Je te montrerai plus tard
  • HorusHorus Membre

    @Céroce, 1 - Pour la closure je verrai dans un second temps pour le moment je renvoi un résultat direct (mais je ne perd pas de vue ce point)


                     2 - Au début j'étais effectivement parti sur cette idée : 



    Api.getUsers { users in ...

    Puis je voulais savoir si avec une fonction "request()" générique avec un renvoi d'objet, de différent type c'était possible (le but n'est pas de réaliser quelque chose, mais plutôt de rechercher s'il y avait possibilité de réaliser ce type de méthode en Swift donc dans un langage typé)


     


    @Joanna Avec plaisir :) 


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #7

    Et voilà  !


     


    Je ne sais pas si j'ai bien pigé ce que tu veuilles mais voici les protocols de base :



    protocol Requestable
    {
    associatedtype RequestType

    func request() -> RequestType
    }

    protocol Buildable
    {
    associatedtype BuiltType

    func build() -> BuiltType
    }

    On commence avec une solution "strictly typed" :



    class User : NSObject
    {
    }

    struct UserRequest : Buildable
    {
    func build() -> User
    {
    return User()
    }
    }

    struct UserQuery : Requestable
    {
    func request() -> UserRequest
    {
    return UserRequest()
    }
    }


    struct UsersRequest : Buildable
    {
    func build() -> [User]
    {
    return [User(), User()]
    }
    }

    struct UsersQuery : Requestable
    {
    func request() -> UsersRequest
    {
    return UsersRequest()
    }
    }


    class Profile : NSObject
    {

    }

    struct ProfileRequest : Buildable
    {
    func build() -> Profile
    {
    return Profile()
    }
    }

    struct ProfileQuery : Requestable
    {
    func request() -> ProfileRequest
    {
    return ProfileRequest()
    }
    }

    Puis nous pouvons essayer une solution générique :



    struct Query<typeT : NSObject> : Requestable
    {
    func request() -> Request<typeT>
    {
    return Request<typeT>()
    }
    }


    struct Request<typeT : NSObject> : Buildable
    {
    func build() -> typeT
    {
    return typeT()
    }
    }

    Ce qui nous donnera le code test suivant



    {
    let user = Api.getUser(id: 1).request().build() // renvoie User

    let users = Api.getUsers().request().build() // renvoie [User]

    let profile = Api.getProfile().request().build() // renvoie Profile

    let user2 = Api.get(id: 1).request().build() as User
    }

    Qu'en penses-tu ?


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #8

    Ou, on pourrait le simplifier comme :



    class Query<typeT : NSObject> : Requestable
    {
    func request() -> Request<typeT>
    {
    return Request<typeT>()
    }
    }

    class Request<typeT : NSObject> : Buildable
    {
    func build() -> typeT
    {
    return typeT()
    }
    }

    class ListQuery<typeT : NSObject> : Requestable
    {
    func request() -> ListRequest<typeT>
    {
    return ListRequest<typeT>()
    }
    }

    class ListRequest<typeT : NSObject> : Buildable
    {
    func build() -> [typeT]
    {
    return [typeT(), typeT()]
    }
    }


    class User : NSObject
    {
    }

    class Profile : NSObject
    {

    }


    struct Api
    {
    static func getUser(id: Int) -> Query<User>
    {
    return Query<User>()
    }

    static func getUsers() -> ListQuery<User>
    {
    return ListQuery<User>()
    }

    static func getProfile(id: Int) -> Query<Profile>
    {
    return Query<Profile>()
    }

    static func get<typeT>(id: Int) -> Query<typeT>
    {
    return Query<typeT>()
    }

    static func getList<typeT>() -> ListQuery<typeT>
    {
    return ListQuery<typeT>()
    }
    }

    Et le code test :



    {
    let user = Api.getUser(id: 1).request().build() // renvoie User

    let users = Api.getUsers().request().build() // renvoie [User]

    let profile = Api.getProfile(id: 1).request().build() // renvoie Profile
    }

    Ou, on peut utiliser la méthode générique sur l'Api :



    {
    let user = Api.get(id: 1).request().build() as User

    let profile = Api.get(id: 1).request().build() as Profile
    }

    Mais il faudrait dire explicitement le type que l'on attend.


  • HorusHorus Membre

    Salut, 


     


    @Joanna, Je n'étais pas dispo ce WE, en tout cas merci pour ta réponse ça m'a l'air vraiment pas mal je regarde ça de suite, je pense que je vais pouvoir l'utiliser comme base pour designer des appels d'API ça à  l'air très propre :D


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #10

    Bien. Mais si t'as des questions ou si j'ai manqué qqch., il faut me demander.


     


    Ce code vient de plusieurs, voire plus de vingt, ans d'expérience avec les génériques. Quand même il y a des soucis avec les protocoles en Swift ; si tu voulais tenir une référence à  un protocole, qui contient un associatedtype ou qui se réfère à  Self, tu tomberas sur un problème bien connu dont la solution est de travailler avec le "type erasure". C'est pas joli, c'est embêtant mais jusqu'au temps qu'Apple fasse qqch. on ne peut faire rien d'autre.


  • HorusHorus Membre

    Je commence juste à  utiliser les "generics", pour ça je souhaitais avoir une bonne base afin de pouvoir les utiliser le mieux possible, si j'ai la moindre question je n'hésiterais pas à  te MP :)


     



     


    si tu voulais tenir une référence à  un protocole, qui contient un associatedtype ou qui se réfère à  Self



     


    C'est exactement ce que j'ai voulu faire et donc l'erreur que j'ai ... x) 


    En tout cas merci, ça m'éclaircis sur pas mal de point !


Connectez-vous ou Inscrivez-vous pour répondre.