Accès aux données par un Singleton

Cette discussion a été créée à partir de réponses séparées de : Livres, eBooks, et documentation Cocoa Objective-C / Interface Builder.

Réponses

  • CéroceCéroce Membre, Modérateur
    janvier 2022 modifié #2

    @MortyMars a dit :

    Mes recherches m'ont amené un temps à m'intéresser à la notion de 'singleton' : serait-ce un moyen efficace de créer une instance bien précise appelable par toute autre classe et pendant toute la durée du cycle de l'application ?

    Oui, ça simplifie souvent l'architecture ce qui explique le succès de cette "pattern".

    // .h
    @interface MyModel: NSObject
    
    +(instancetype) sharedModel;
    
    // On pourrait éventuellement empêcher les appels à +[alloc] et +[new]
    
    @end
    
    
    // .m
    @implementation MyModel
    
    +(instancetype) sharedModel {
        static dispatch_once_t onceToken;
        static MyModel *sharedInstance = nil;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[MyModel alloc] init];
        });
        return sharedInstance;
    }
    
    @end
    

    Cependant, il faut connaître ses limites:

    • par principe, cela concerne les classes qui n'ont qu'une seule instance dans l'appli. Des exemples dans AppKit: NSScreen, NSFontPanel. Est-ce qu'il n'y a qu'une seule instance du modèle dans l'appli? Ça se discute (moi je dis que non).
    • d'un point de vue architectural, cette classe devient une dépendance de toutes les classes qui l'utilisent.
    • ça complique l'implémentation des tests unitaires, puisqu'il s'agit d'une dépendance cachée.
    • attention, c'est presque une variable globale => difficile à maintenir

    Ceci dit, il faut bien que tu commences quelque part, et si ça résout ton problème, alors c'est une solution commode qui te permet d'avancer.

  • Merci pour cette confirmation et pour l'exemple de code :)
    Je teste ça dès ce soir et ferai un retour.
    A+ ;)
  • @Draken a dit :
    A vrai dire, la programmation iOS/Mac a été unifié avec SwiftUI. Avant c'était carrément le bordel !

    Merci, j'ai bien ri !

    Sinon pour le singleton il est bon de relire les justes mots de notre ami @Céroce. J'ajouterai qu'on a vite fait de tomber dans le piège du singleton et en mettre partout parce que c'est pratique sur le coup.

    J'ai toujours suivit cette règle pour les singletons : ne les utiliser que s'ils donnent accès à une resource unique : une connexion vers une DB ou une API, une interface vers StoreKit pour gérer les iap et les abonnements.

    Et c'est exactement ce pour quoi tu les trouvera employés dans UIKit/Cocoa. SwiftUI diffère un peu sur ce point préférant le concept d'environnement qui ne repose pas sur des singletons, du moins aucun que tu ne peux manipuler directement.

  • Le singleton est pratique en effet.
    Je les limite aussi, faut pas en abuser.

    En général, dans l'apprentissage, on galère, puis une fois qu'on commence à maîtriser un peu, on utiliser à foison les singletons, et enfin, on essaye de s'en libérer (pas totalement), mais en partie.

    Ce qui peut-être pratique aussi, afin de prévoir la suite, c'est de ne pas forcément faire appel à eux directement, de toujours les mettre en accès via une propriété.
    En bref, plutôt que de dire à chaque fois :

    @implementation MyClass
    
    -(void)myFunction {
        [[MySingletonForWebAPI sharedInstance] doSomething];
    }
    @end
    
    @implementation MyClass
    -(MySingletonForWebAPI *)apiClient {
        return [MySingletonForWebAPI sharedInstance];
    }
    -(void)myFunction {
        [[self apiClient] doSomething];
    }
    @end
    

    Tu peux avoir :

    C'est l'étape je pense, post folie du singleton, car tu seras tenté de mettre [MySingletonForWebAPI sharedInstance] partout dans ton code. Comme ça, tu pourras filer en paramètre d'init ou après init, l'API Client, qui ne sera pas forcément un singleton. Cela devrait faciliter tes tests unitaires en tout cas.

  • @Céroce a dit :neutral:

    • par principe, cela concerne les classes qui n'ont qu'une seule instance dans l'appli. Des exemples dans AppKit: NSScreen, NSFontPanel. Est-ce qu'il n'y a qu'une seule instance du modèle dans l'appli? Ça se discute (moi je dis que non).

    Merci pour le code de la classe singleton qui compile sans pb ; mais au vu des résultats que j'obtiens (qui se résument à pas grand chose), j'avoue ne pas être certain d'adopter la bonne méthode d'appel de cette instance unique...

    Mon problème d'approche est sans doute plus général (et/ou peut-être simplement basique) vis-à-vis des classes et instances Objective-C et je devrais probablement repartir de la question initiale, que je resynthétise comme suit :

    • J'ai créé une classe contrôleur pour gérer les objets d'une interface avec outlet (pour un TextField) et action (pour un NSbutton) qui vont bien
    • J'en ai déposé une instance dans mon xib
    • Quand je drag cette instance dans le code de mon AppDelegate pour y créer une @property accessible, tout fonctionne comme je le souhaite : le contrôleur est créé et ses méthodes sont efficientes.
    • Mais quand je drag cette même instance dans une autre classe (et plus dans AppDelegate) l'outlet est systématiquement nil et plus rien ne fonctionne malgré les connexions qui sont OK vu du code.

    Pour quelle raison un même code d'appel à ce contrôleur fonctionne dans une classe et pas dans une autre ?
    Sans doute une spécificité d'AppDelegate qui m'échappe…

  • Merci à Larme, que je n'avais pas lue avant mon précédent commentaire, et qui répond à la question de comment appeler l'instance unique du singleton...

    Je poursuis et vous tiens au courant de mes avancées ;)

  • Je ne fais pas du macOS, mais c'est un problème qu'on peut rencontrer sur iOS : mes IBOutlets sont nils sur ma vue/viewcontroller ou autre élément qui vient d'InterfaceBuilder
    En général, les erreurs communes sont :
    J'ai oublié de faire la connexion entre vue et classe.
    Comment est initialisé la vue/viewcontroller ? En bref, quand j'initialise mon élément, il peut s'initialiser par mon propre code (j'ai fait moi même [[Element alloc] init]) ou par code caché (via un segue par exemple). Donc est-ce que je l'ai initialisé en lui disant d'associer tout ce que j'ai fait dans InterfaceBuilder ? Là, est souvent l'erreur.
    Si c'est dans un Storyboard, il faut chercher comment initialiser un ViewController depuis un Storyboard.
    Si c'est un Xib, quel est le nom du xib (il y a un initWithNibName: avec lequel il devrait être initialisé, et un nom implicite au besoin, mais j'aime l'écrire pour être sûr), etc.
    Et un peu lié à précédemment, est-ce que tu parles à la même instance ? Je veux dire, est-ce que l'élément que tu manipules est bien celui auquel tu penses (la vue qui est à l'écran ?).

  • @Larme a dit :
    En général, les erreurs communes sont :
    J'ai oublié de faire la connexion entre vue et classe.

    Les connexions sont faites et paraissent actives graphiquement (dot circled)...

    Comment est initialisé la vue/viewcontroller ? En bref, quand j'initialise mon élément, il peut s'initialiser par mon propre code (j'ai fait moi même [[Element alloc] init]) ou par code caché (via un segue par exemple). Donc est-ce que je l'ai initialisé en lui disant d'associer tout ce que j'ai fait dans InterfaceBuilder ? Là, est souvent l'erreur.

    L'initialisation est explicite dans le code, mais s'il y avait un pb à ce niveau pourquoi ça matcherait dans le cas AppDelegate et pas dans l'autre ?

    Si c'est un Xib, quel est le nom du xib (il y a un initWithNibName: avec lequel il devrait être initialisé, et un nom implicite au besoin, mais j'aime l'écrire pour être sûr), etc.

    C'est une piste que je n'ai pas encore explorée...
    Il faut que j'y regarde de plus près sachant que je crois comprendre qu'AppDelegate intervient implicitement dans l'initialisation du xib...

    Et un peu lié à précédemment, est-ce que tu parles à la même instance ? Je veux dire, est-ce que l'élément que tu manipules est bien celui auquel tu penses (la vue qui est à l'écran ?).

    Je crois pouvoir être affirmatif en disant que oui puisque dans les deux tests je drag le même 'cube bleu', tantôt dans mon AppDelegate, tantôt dans une autre de mes classes...

    Merci pour ton aide :)

  • Bonjour à tous,

    J'ai résolu mon problème, sans doute d'une manière peu élégante voire critiquable, en créant une variable globale du type d'une instance de mon contrôleur, que j'ai initialisée comme étant l'objet contrôleur instancié par AppDelegate.
    Pas sûr d'avoir été clair sur ce coup..., mais j'ai accès désormais -via ce 'contrôleur global'- à mon interface graphique et ses contrôles, à n'importe quel endroit du code et pendant toute la durée de vie de l'application.
    L'idée du singleton, sans doute plus orthodoxe, m'avait séduit mais n'avait pas résolu mon souci puisque la création de la 'SharedInstance' n'intervenait qu'après l'instanciation par AppDelegate du premier contrôleur ayant la main sur l'UI et je n'ai pas été capable d'associer les deux...
    Merci pour toutes vos pistes en tous cas ;)

  • CéroceCéroce Membre, Modérateur

    Ça ira pour l'instant… Tant que tu avances, c'est bon!
    Bon courage pour la suite, mais tu as fait presque le plus dur; si nous n'avons pas pu t'aider c'est qu'Apple ne nous donne pas un mécanisme clair pour accéder au window controller depuis le storyboard. La plateforme macOS n'est plus une priorité pour Apple depuis des années, et la documentation est inexistante sur certains aspects, ça n'aide pas.
    Pour la suite, tu vas tomber sur des éléments qui ont peu changé depuis 2001 ou des choses qui fonctionnent comme sous iOS, ça devrait mieux se passer.

  • Merci Céroce pour l'oeil que tu gardes sur ce fil et pour tes encouragements.
    Et oui ça fait du bien de passer un obstacle sur lequel on butait depuis trop longtemps, même si on s'est occupé à d'autres soucis en attendant ;)

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