Aller au contenu


Photo

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


  • Please log in to reply
10 réponses à ce sujet

#1 Horus

Horus

    Cueilleur de cabosses

  • Membre
  • PipPipPip
  • 172 messages
  • LocationLille

Posté 19 mai 2017 - 12:03

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


Horus,


#2 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 325 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 19 mai 2017 - 12:32

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"))

RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#3 Horus

Horus

    Cueilleur de cabosses

  • Membre
  • PipPipPip
  • 172 messages
  • LocationLille

Posté 19 mai 2017 - 13:28

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 


Horus,


#4 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 325 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 19 mai 2017 - 14:19

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.
RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#5 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 999 messages
  • LocationPlestin-les-Grèves (22)

Posté 19 mai 2017 - 14:56

Il te faut une classe générique. Je fais actuellement les courses. Je te montrerai plus tard

#6 Horus

Horus

    Cueilleur de cabosses

  • Membre
  • PipPipPip
  • 172 messages
  • LocationLille

Posté 19 mai 2017 - 15:25

@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 :) 


Horus,


#7 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 999 messages
  • LocationPlestin-les-Grèves (22)

Posté 19 mai 2017 - 21:23

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 ?



#8 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 999 messages
  • LocationPlestin-les-Grèves (22)

Posté 19 mai 2017 - 21:51

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.



#9 Horus

Horus

    Cueilleur de cabosses

  • Membre
  • PipPipPip
  • 172 messages
  • LocationLille

Posté 22 mai 2017 - 08:45

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


Horus,


#10 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 999 messages
  • LocationPlestin-les-Grèves (22)

Posté 22 mai 2017 - 10:45

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.



#11 Horus

Horus

    Cueilleur de cabosses

  • Membre
  • PipPipPip
  • 172 messages
  • LocationLille

Posté 22 mai 2017 - 10:52

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 !


Horus,





0 utilisateur(s) li(sen)t ce sujet

0 membre(s), 0 invité(s), 0 utilisateur(s) anonyme(s)