Singletons: un peu, beaucoup, à  la folie, pas du tout



Joanna Carter, tu as trouvé LA solution !


 


En effet, si la carte est directement affichée avec un tab bar controller, elle n'est chargée qu'une seule fois, et donc, il n'y a pas de souci de mémoire.


 


Or, j'aimerai tout de même faire quelque chose qui se rapproche de ce que j'avais fait au départ.


J'ai pensé à  une solution : au lieu de supprimer les UIViewController du Navigation Controller, je pourrai tout simplement simuler un click sur l'item de la carte ou sur un autre item pour revenir. Le problème est que je ne parviens pas, avec le code, à  récupérer la tab bar que j'ai créé dans mon storyboard. Comment faire ?




C'est bien compliqué ton système. Tu pourrais aussi charger la carte en mémoire au lancement de l'application, et la stocker dans un singleton, ou une variable globale (je sais, les variables globales c'est le mal). 

«1

Réponses

  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #2

    Tu pourrais aussi charger la carte en mémoire au lancement de l'application, et la stocker dans un singleton, ou une variable globale (je sais, les variables globales c'est le mal).

    1) les singletons sont seulement l'incarnation Objet des variables globales, c'est tout aussi mal.
  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2017 modifié #3


    1) les singletons sont seulement l'incarnation Objet des variables globales, c'est tout aussi mal.




     


    Je ne comprends pas cet crainte des singletons.


     


    Si on est disposé d'utiliser :



    UIApplication.shared.delegate as? AppDelegate

    ... qui est, effectivement, un singleton, pourquoi pas créer vos propres singletons ?


     


    Et pourquoi polluer le délégué de l'application avec les entités comme les données ?


     


    Or, les base de données sont souvent un "lieu de stockage" unique ; du coup, c'est logique de créer un point d'accès unique avec un singleton


  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #4

    Je ne comprends pas cet crainte des singletons.

    https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons

    J'ai arrêté d'utiliser les singletons le jour où je me suis mis à  écrire des tests unitaires.
  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2017 modifié #5


    J'ai arrêté d'utiliser les singletons le jour où je me suis mis à  écrire des tests unitaires.



    Donc je présume que tu n'utilises jamais l'application delegate, qui est associé avec UIApplication.shared, qui est un singleton
  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #6
    UIApplication est un singleton. ça se discute, puisque certes, il n'existe forcément qu'une seule instance pour toute l'application, mais ça rend les tests unitaires difficiles à  écrire.

    Pour ce qui est de l'AppDelegate, c'est évidemment mon point d'entrée dans le programme, et souvent c'est bien là  que je vais instancier le Modèle, puisque l'AppDelegate est le contrôleur racine de l'application. Ainsi, il pourra passer le Modèle aux ViewControllers.

    Et je dis bien "passer". Dans mon modèle "tell, don't ask", aucun objet n'a de raison de demander quoi que ce soit à  l'AppDelegate. Les flux de données vont forcément vers l'avant.

    À noter que l'AppDelegate est difficilement testable, parce qu'on ne peut pas lui passer un objet Application, du moins en Swift. En ObjC, on peut utiliser OCMock pour mocker UIApplication.
  • LeChatNoirLeChatNoir Membre, Modérateur

    Je vais peut être dire une grosse connerie mais pas grave : Coredata fonctionne pas avec des singletons ?


     


    Perso, j'utilise toujours CoreData + MagicalRecord (oui, je sais, ça fait un peu vieux mais ça fonctionne bien, même en swift !) et quand je veux accèder à  ma base, je fais un simple :



    [Site MR_findAll];

    ou en swift :



    Site.findAllSortedBy("name", ascending: ascending)

    C'est pas du bon gros Singleton ça ?


     


    :)


  • ça ressemble plus à  des propriétés et méthodes de classe non ?


  • LeChatNoirLeChatNoir Membre, Modérateur
    novembre 2017 modifié #9

    Je sais pas trop parce que par exemple, si je fetch une fois ma base, pour les fetch suivants, je sais qu'il va réutiliser les précédents.


     


    Alors est ce que c'est du singleton, est ce que c'est du "lazy load"... Je sais pas trop. J'ai jamais trop creusé...


     


    En tous cas, c'est pratique.


     


    SInon, perso, j'utilise un singleton pour les achats inApp dans mon app.

  • Joanna CarterJoanna Carter Membre, Modérateur


    Je vais peut être dire une grosse connerie mais pas grave : Coredata fonctionne pas avec des singletons ?


     


    Perso, j'utilise toujours CoreData + MagicalRecord (oui, je sais, ça fait un peu vieux mais ça fonctionne bien, même en swift !) et quand je veux accèder à  ma base, je fais un simple :



    [Site MR_findAll];

    ou en swift :



    Site.findAllSortedBy("name", ascending: ascending)

    C'est pas du bon gros Singleton ça ?


     


    :)




     


    Pas exactement. Là , tu utilises les méthodes "statiques" ou "classes".


     


    Moi, je n'aime pas cette démarche parce qu'elle signifie que la classe sache comment trouver et trier tous les instances du type ; ce que l'on n'attend pas. Ce n'est pas le type que s'occupe de ça, c'est le mechanism de stockage et c'est lui qu'il faut demander.

  • Joanna CarterJoanna Carter Membre, Modérateur
    novembre 2017 modifié #11

    Pour faciliter les tests unitaires avec un Singleton, on peut toujours fournir une méthode internal que l'on puisse accéder dans les test en utilisant @testable import


     


    Dans cet exemple, j'ai créé un deuxième init paramétré pour que je puisse fournir un état connu :



    public struct DataProvider
    {
    private var state: Any?

    private init() { }

    internal init(state: Any?)
    {
    self.state = state
    }

    public static let shared = DataProvider()
    }

  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #12

    Je vais peut être dire une grosse connerie mais pas grave : Coredata fonctionne pas avec des singletons ?

    Non, tu peux tout à  fait avoir plusieurs bases de données, et plusieurs MOC.

    MagicalRecords essaie de tout faciliter au maximum, d'où la proposition de passer par un singleton pour accéder au MOC.
  • JérémyJérémy Membre
    novembre 2017 modifié #13


    Moi, je n'aime pas cette démarche parce qu'elle signifie que la classe sache comment trouver et trier tous les instances du type ; ce que l'on n'attend pas. Ce n'est pas le type que s'occupe de ça, c'est le mechanism de stockage et c'est lui qu'il faut demander.




     


    Autant pour l'utilisation des singletons je suis entièrement d'accord avec toi. En revanche je ne partage pas ton point de vue sur tes derniers propos. Le but de la fonction static est d'offrir une action extrinsèque à  aux instances de sa classe. Dans le cas présenté la méthode findAllSortedBy retourne une liste qui n'est en aucun cas lié à  l''état d'une instance de la classe Site.



    Site.findAllSortedBy("name", ascending: ascending) 

  • CéroceCéroce Membre, Modérateur

    Pour faciliter les tests unitaires avec un Singleton, on peut toujours fournir une méthode internal que l'on puisse accéder dans les test en utilisant @testable import
     
    Dans cet exemple, j'ai créé un deuxième init paramétré pour que je puisse fournir un état connu :


    public struct DataProvider
    {
    private var state: Any?

    private init() { }

    internal init(state: Any?)
    {
    self.state = state
    }

    public static let shared = DataProvider()
    }


    Dans ton exemple, l'objet de test est forcement un DataProvider. ça pose quand même problème, parce qu'on ne peut pas le remplacer par ce qu'on veut. En test unitaires, on cherche à  placer l'objet qu'on teste dans l'environnement le plus cloisonné possible. S'il faut instancier des objets complexes à  créer:
    1) les tests deviennent difficiles à  écrire
    2) les tests sont à  la merci du fonctionnement interne de ces objets, et ne sont donc pas fiables.
  • JérémyJérémy Membre
    novembre 2017 modifié #15


    https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons


    J'ai arrêté d'utiliser les singletons le jour où je me suis mis à  écrire des tests unitaires.




     


    Le gros problème du singleton réside essentiellement dans la sa gestion face à  un contexte "multithreadé".


     


    Dans une application je ne trouve pas déconnant que les instances des classes de ta couche service et persistance soient uniques pour les raisons évoquées par Joanna. Je ne vois vraiment pas la plus value de créer un objet dans chaque controllers pour accéder au datas (par exemple) dans la mesure où l'utilisateur est seul sur son device.


  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #16

    Je ne vois vraiment pas la plus value de créer un objet dans chaque controllers pour accéder au datas (par exemple) dans la mesure où l'utilisateur est seul sur son device.

    Ce n'est absolument pas le cas. Dans mon cas aussi, il n'y a qu'une instance de l'objet. La différence est que cet objet est passé à  l'objet qui l'utilise, de manière explicite.

    Avec Singleton:
    class DatabaseStatistics {

    func statistics() -> String {
    let db = Database.shared
    return "There are \(db.entries.count) entries."
    }
    }
    Avec Injection de dépendance:
    class DatabaseStatistics {
    let database: Database
    init(database: Database) {
    self.database = database
    }


    func statistics() -> String {
    return "There are \(database.entries.count) entries."
    }
    }
    Peut-être que la version avec le singleton paraà®t plus simple, mais en pratique, elle a une dépendance cachée sur Database, et on ne peut pas remplacer facilement la Database dans les tests.
  • JérémyJérémy Membre
    novembre 2017 modifié #17


    Ce n'est absolument pas le cas.




     


    C'était un exemple.  :)


     




    Dans mon cas aussi, il n'y a qu'une instance de l'objet. La différence est que cet objet est passé à  l'objet qui l'utilise, de manière explicite.


    Avec Singleton:



    class DatabaseStatistics {

    func statistics() -> String {
    let db = Database.shared
    return "There are \(db.entries.count) entries."
    }
    }

    Avec Injection de dépendance:

    class DatabaseStatistics {
    let database: Database
    init(database: Database) {
    self.database = database
    }


    func statistics() -> String {
    return "There are \(database.entries.count) entries."
    }
    }



     


    Je ne vois pas en quoi ton second exemple met en cause l'emploi des singletons.


     


    Tu peux très bien faire :



    DatabaseStatistics(database: Database.shared)

    Mais le gros avantage de ta solution réside dans la souplesse de ton service qui sera facilement mockable dans les TU. Mais encore une fois, je ne vois pas en quoi l'emploi du singleton doit être prohibé.


  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #18

    Mais encore une fois, je ne vois pas en quoi l'emploi du singleton doit être prohibé.

    Je ne parle pas de le prohiber, mais ne serait-ce que réfléchir si cet emploi est vraiment judicieux. En pratique il y a très peu d'objets qui ont des raisons d'être vraiment uniques dans une application. Depuis le début, vous me donnez l'exemple de la Base de données, mais justement, je ne vois pas pourquoi elle serait unique dans une application. Et si j'ai envie de dupliquer un document, d'en extraire une partie, de la migrer, de travailler en mémoire ? Je comprends bien que c'est plus facile... au début.

    Cette design pattern impose des contraintes inutiles pour les tests, et elle encourage de mauvaises pratiques, telles que masquer les dépendances et coupler fortement les classes.

    Il y a 20 ans, je codais en utilisant les variables globales et si quelqu'un était venu me dire que j'avais tort de faire ainsi, je lui aurais répondu: " En quoi les globales doivent être prohibées? ça marche, et c'est facile! ". Cependant, le fruit de l'expérience est venu me démontrer qu'en grossissant, le programme devenait ingérable: ça plantait tout le temps, je passais des heures sur le débogueur, et plus rien n'avançait. (à  l'époque, un plantage d'un programme obligeait souvent à  redémarrer le Mac).

    ça m'a donné une bonne leçon, mais chacun doit faire son propre chemin pour comprendre ce genre de choses. À la suite de cette expérience, je fus capable d'énoncer clairement quels étaient les inconvénients des variables globales, leur avantages et les solutions alternatives.

    Pour les singletons, vous connaissez les avantages, je vous expose les inconvénients et les solutions alternatives. Comme pour les globales, il faut déjà  avoir un programme assez gros pour comprendre les problèmes qu'ils suscitent.


  • À présent, vous pouvez soit croire que ma position sur les singletons est issu d'un dogme, soit envisager que je suis allé plus loin que vous sur ce chemin.




     


    ???  ???  ??? 


     


    C'est sérieux comme phrase ? Ou un excès de contrariété qui n'a pas vraiment lieu d'être ?


     




    Pour les singletons, vous connaissez les avantages, je vous expose les inconvénients et les solutions alternatives. Comme pour les globales, il faut déjà  avoir un programme assez gros pour comprendre les problèmes qu'ils suscitent.




     


    Je ne dis pas le contraire. Restons sur le cas des singletons.


     




    Cette design pattern impose des contraintes inutiles pour les tests, et elle encourage de mauvaises pratiques, telles que masquer les dépendances et coupler fortement les classes.




     


    Ce que tu dis est loin d'être faux, seulement tu démontes toi même tes arguments en donnant l'exemple ci-dessous :

     





    class DatabaseStatistics {
    let database: Database
    init(database: Database) {
    self.database = database
    }


    func statistics() -> String {
    return "There are \(database.entries.count) entries."
    }
    }



     


    - Facilement testable grâce à  l'injection de dépendance.


    - Couplage beaucoup moins fort (si Database était un protocol ce serait encore mieux).

    - La dépendance n'est pas masquée.


     


    J'ai vraiment du mal à  te suivre dans ton argumentation.  :)

  • CéroceCéroce Membre, Modérateur

    Ce que tu dis est loin d'être faux, seulement tu démontes toi même tes arguments en donnant l'exemple ci-dessous :

    C'est justement l'exemple sans Singleton.
  • JérémyJérémy Membre
    novembre 2017 modifié #21


    C'est justement l'exemple sans Singleton.




     


    Mais rien ne t'empêche d'utiliser un singleton avec l'injection de dépendance.


     


    Je me répète mais tu peux très bien faire ça :



    DatabaseStatistics(database: Database.shared)

    La façon dont tu nous le montres me donne l'impressions que ta proposition d'injection de dépendance est une alternative à  l'utilisation des singletons... C'est juste complémentaire, tu peux très bien injecter un singleton avec ta solution.


  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #22
    On peut même écrire ça:
    class DatabaseStatistics {
    let database: Database
    init(database: Database = Database.shared) {
    self.database = database
    }


    func statistics() -> String {
    return "There are \(database.entries.count) entries."
    }
    }
    Et je suis 100% d'accord avec toi que ça résout le problème du test.
     

    La façon dont tu nous le montres me donne l'impressions que ta proposition d'injection de dépendance est une alternative à  l'utilisation des singletons... C'est juste complémentaire, tu peux très bien injecter un singleton avec ta solution.

    Oui j'affirme que c'est bien une alternative, parce qu'il n'y a pas d'intérêt à  injecter une dépendance si on peut accéder à  cette dépendance par son singleton. As-tu déjà  vu beaucoup de code comme ci-dessus ?

    (Moi, j'en ai déjà  vu, mais écrit par quelqu'un qui justement était ennuyé par UserDefaults dans ses tests unitaires).


  • parce qu'il n'y a pas d'intérêt à  injecter une dépendance si on peut accéder à  cette dépendance par son singleton. 




     


    Peut être à  rendre le couplage entre les classes moins fort ?


     


    Au delà  de ça, quand tu as des services (ou je ne sais trop quoi d'autre), tu vas créer une instance par controller ?

  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #24

    Au delà  de ça, quand tu as des services (ou je ne sais trop quoi d'autre), tu vas créer une instance par controller ?

    Non, par exemple pour un WebService, je vais créer une seule instance et la passer aux contrôleurs qui en ont besoin. Je pense que ce qui se discute dans cette solution " plus que le fait de ne pas utiliser un singleton " est son aspect monolithique, avec un seul objet central qui fait un peu trop de choses.

    Un exemple où j'utiliserais un Singleton: le logging. Logguer n'est pas sensé modifier le fonctionnement de la classe et filer un objet Logging à  tout le monde devient carrément lourd; il y en a qui vont jusque là , voire même qui testent que ça loggue bien, mais je doute du retour sur investissement. En pratique NSLog() me suffit souvent.
  • Ah okay d'accord !  :)


     


    Juste pour bien comprendre ta façon de procéder. Si tu as une couche de service et couche de persistance (le service "travaille" les datas avant de les persister). Tu dois injecter la classe de persistance dans ta classe de service. Où fais tu ça ? Dans ton controller ? Ou tu passes pas une classe tiers qui fait les injections et qui te retourne ton service correctement construit ?


  • Joanna CarterJoanna Carter Membre, Modérateur


    Dans ton exemple, l'objet de test est forcement un DataProvider. ça pose quand même problème, parce qu'on ne peut pas le remplacer par ce qu'on veut. En test unitaires, on cherche à  placer l'objet qu'on teste dans l'environnement le plus cloisonné possible. S'il faut instancier des objets complexes à  créer:

    1) les tests deviennent difficiles à  écrire

    2) les tests sont à  la merci du fonctionnement interne de ces objets, et ne sont donc pas fiables.




     


     


    Bah non ! Le Data Provider singleton n'est qu'un emballage autour un mécanisme de stockage qui pourrait être remplacé.


     


    Du coup, on peut tester les mécanisme  à  part du singleton et, après, tester la fonctionnalité du singleton.


     


    Je attendrais qqch. comme :



    public protocol BaseObject
    {

    }


    public protocol DataMechanism
    {
    func createNew<T : BaseObject>() throws -> T

    func fetch<T : BaseObject>() throws -> [T]

    func fetch<T : BaseObject>(for predicate: NSPredicate) throws -> [T]

    func save<T : BaseObject>(object: T)

    func delete<T : BaseObject>(object: T)
    }


    struct CoreDataMechanism : DataMechanism
    {
    func createNew<T : BaseObject>() throws -> T
    {
    // ...
    }

    func fetch<T : BaseObject>() throws -> [T]
    {
    // ...
    }

    func fetch<T : BaseObject>(for predicate: NSPredicate) throws -> [T]
    {
    // ...
    }

    func save<T : BaseObject>(object: T)
    {

    }

    func delete<T : BaseObject>(object: T)
    {

    }
    }


    struct XMLMechanism : DataMechanism
    {
    func createNew<T : BaseObject>() throws -> T
    {
    // ...
    }

    func fetch<T : BaseObject>() throws -> [T]
    {
    // ...
    }

    func fetch<T : BaseObject>(for predicate: NSPredicate) throws -> [T]
    {
    // ...
    }

    func save<T : BaseObject>(object: T)
    {

    }

    func delete<T : BaseObject>(object: T)
    {

    }
    }


    public protocol DataProviderProtocol
    {
    static var shared: Self { get }
    }


    public struct DataProvider
    {
    private var mechanism: DataMechanism?

    internal init() { }

    public mutating func setMechanism(_ mechanism: DataMechanism)
    {
    self.mechanism = mechanism
    }

    public static var shared = DataProvider()

    func createNew<T : BaseObject>() throws -> T
    {
    guard let mechanism = mechanism else
    {
    // throw error
    }

    return try mechanism.createNew()
    }

    func fetch<T : BaseObject>() throws -> [T]
    {
    guard let mechanism = mechanism else
    {
    // throw error
    }

    return try mechanism.fetch()
    }

    func fetch<T : BaseObject>(for predicate: NSPredicate) throws -> [T]
    {
    guard let mechanism = mechanism else
    {
    // throw error
    }

    return try mechanism.fetch(for: predicate)
    }

    func save<T : BaseObject>(object: T)
    {
    guard let mechanism = mechanism else
    {
    // throw error
    }

    mechanism.save(object: object)
    }

    func delete<T : BaseObject>(object: T)
    {
    guard let mechanism = mechanism else
    {
    // throw error
    }

    mechanism.delete(object: object)
    }
    }

    Puis, au commencement de l'Appli :



    {
    let coreDataMechanism = CoreDataMechanism()

    DataProvider.shared.setMechanism(coreDataMechanism)

    // ou

    let xmlMechanism = XMLMechanism()

    DataProvider.shared.setMechanism(xmlMechanism)
    }

    et, après :



    {
    do
    {
    let voitures = try DataProvider.shared.fetch() as [Voiture]

    ...
    }
    catch
    {
    print("fetch failed")
    }
    }
  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #27

    Juste pour bien comprendre ta façon de procéder. Si tu as une couche de service et couche de persistance (le service "travaille" les datas avant de les persister). Tu dois injecter la classe de persistance dans ta classe de service. Où fais tu ça ? Dans ton controller ? Ou tu passes pas une classe tiers qui fait les injections et qui te retourne ton service correctement construit ?


    Je ne vais pas te donner une réponse précise, parce que... ça dépend. La persistance des données est un point sur lequel pêche la POO. Je ne connais pas de solution parfaite.

    Le cas simple, c'est le JSON/plist/XML: chaque classe se décrit elle-même puis ses enfants en renvoyant un dictionnaire. Et peut aussi s'initialiser avec. C'est un contrôleur qui va demander l'instanciation à  partir de la NSData, mais pas forcément un View Controller.

    Pour une base de données, on peut imaginer avoir une sorte de Pool qui se débrouille pour instancier ou sauvegarder des objets Modèle, mais cette solution n'a pas ma faveur, parce que l'objet Pool devient vite chargé et peu réutilisable.

    Si possible, je préfère que chaque objet soit capable de s'enregistrer lui-même dans la BdD, et lui dire objet.update(in: db). L'avantage est que c'est plus léger, et bien localisé. L'inconvénient est qu'il n'y a pas une séparation claire entre la couche Métier et la couche Persistance.

    Je ne suis pas sûr d'avoir répondu à  ta question. Mais disons que mes contrôleurs sont peu concernés par la persistance qui est du ressort de la couche Modèle. Ce sont tout de fois bien les instigateurs des opérations de lecture et enregistrement.
  • CéroceCéroce Membre, Modérateur

    Du coup, on peut tester les mécanisme  à  part du singleton et, après, tester la fonctionnalité du singleton.


    Le problème n'est pas de tester le singleton, mais de tester les objets qui utilisent le singleton.

    Un exemple simpliste:

    class DateDisplay {
    func localizedStringFor(date: Date) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.locale = Locale.current // Accès au singleton Locale
    return dateFormatter.string(from: date)
    }
    }

    class DateDisplayTests: XCTestCase {
    func testReturnsLocalizedDateString() {
    let date = // Composée à  partir des composants, pour que le test soit reproductible
    let dateDisplay = DateDisplay()
    XCTAssertEqual(dateDisplay.localizedStringFor(date: date), "Lundi 6 novembre 2017")
    }

    }
    Le test passe parfaitement chez moi, mais pas chez mon collègue Allemand, puisque sa machine a une Locale différente.

    On peut ici résoudre le problème en passant une Locale explicitement, comme proposé par Jérémy:


    class DateDisplay {
    func localizedStringFor(date: Date, locale: Locale = Locale.current) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.locale = locale
    return dateFormatter.string(from: date)
    }
    }

    class DateDisplayTests: XCTestCase {
    func testReturnsLocalizedDateString() {
    let date = // Composée à  partir des components, pour que le test soit reproductible
    let locale = Locale(identifier: "fr")
    let dateDisplay = DateDisplay()
    XCTAssertEqual(dateDisplay.localizedStringFor(date: date, locale: locale), "Lundi 6 novembre 2017")
    }

    }
    Un exemple qui pose vraiment problème dans les tests, c'est quand on veut s'assurer qu'une méthode a bien été appelée sur le singleton. Je vous montrerai un autre jour.


  • Un exemple qui pose vraiment problème dans les tests, c'est quand on veut s'assurer qu'une méthode a bien été appelée sur le singleton. Je vous montrerai un autre jour.




     


    Ah ? Quand ?  :D


     


    Dans un même ordre d'idée j'aurais aimé avoir votre avis dans le but de me proposer une meilleur solution. Je fabrique une partie de mes interfaces à  la mano (entendre par là  sans interface builder). Pour définir les marges entre les composants et dans un but d'avoir une interface cohérente et propre j'ai créé une classe qui s'apparente au code ci-dessous :



    internal struct UIProperty {
    internal let static shared = UIProperty()

    internal let gap: CGFloat

    private init() {
    self.gap = CGFloat(8.0)
    }
    }

    Pour l'utiliser j'ai juste à  écrire le code suivant :



    NSLayoutConstraint(item: self.myView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: CGFloat(1.0), constant: UIProperty.shared.gap).isActive = true

    Mais nous sommes bien d'accord que, derrière un objet employant le design pattern singleton, nous sommes face à  une variable globale ce qui n'est pas forcément top... D'ailleurs la frontière entre singleton et variable globale est souvent très mince.


     


    L'idée est d'avoir à  toucher le paramètre gap pour modifier l'apparence générale de l'application sans modifier une à  une les différentes contraintes graphiques.


     


    Auriez vous quelque chose de mieux à  me proposer ?  :)

  • CéroceCéroce Membre, Modérateur
    novembre 2017 modifié #30
    Pour commencer, note qu'une autre solution techniquement proche est d'utiliser des constantes statiques.
    L'inconvénient évident de ta solution est qu'on ne peut pas changer de UIProperty. Par exemple, si tu veux que le gap soit différent juste pour un composant.

    L'alternative serait alors de passer l'UIProperty au composant graphique, par exemple à  son init. ça supprime le singleton, mais évidemment, ça demande de passer l'UIProperty de proche en proche. Je ne dirais pas que c'est une meilleure solution.

    Personnellement, je ne ferais ici de l'injection de dépendance que si je voulais écrire des tests unitaires. Mais dans ce cas précis, un tests UI serait peut-être plus indiqué.
  • Oui je vois le truc. A la limite je pourrais faire quelque chose comme ça :



    [...]
    var uiProperty: UIProperty {
    didSet {
    myLayout.constant = uiProperty.gap
    }
    }
    [...]

    Après j'avais également pensé à  mettre une propriété calculé dans une extension de la classe UIView que j'aurais pu surcharger dans le cas d'une vue qui aurait un gap spécifique. 


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