J'ai une classe singleton dans mon application, déclarée selon le singleton d'une ligne (avec un init ()
privé) dans ce billet de blog . Plus précisément, cela ressemble à ceci:
NSLog("aProperty: [\(singleton!.aProperty),\(String(describing:singleton!.value(forKey: "aProperty"))),\(Singleton.sharedInstance.singleton),\(String(describing:Singleton.sharedInstance.value(forKey: "aProperty")))] hidden: \(myMenuItem.isHidden)")
Je voudrais lier l'état de aProperty
à savoir si un élément de menu est masqué.
Voici les étapes que j'ai suivies pour ce faire:
Accédez à la bibliothèque d'objets dans Interface Builder et ajoutez un "objet" générique à ma scène d'application. Dans l'inspecteur d'identité, configurez "Class" sur Singleton
.
Créez une prise de référence dans mon délégué d'application en faisant glisser la touche Ctrl de l'objet singleton dans Interface Builder vers le code de mon délégué d'application. Cela finit par ressembler à ceci:
singleton init singleton init sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)
aProperty code > sous "Chemin de la clé du modèle".
Malheureusement, cela ne fonctionne pas: la modification de la propriété n'a aucun effet sur l'élément de menu en question.
Le problème semble être que, malgré la déclaration de init ()
comme privé, Interface Builder parvient à créer une autre instance de mon singleton. Pour le prouver, j'ai ajouté NSLog ("singleton init")
à la méthode privée init ()
ainsi que le code suivant à applicationDidFinishLaunching () code > dans mon délégué d'application:
NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")
Lorsque j'exécute l'application, ceci est affiché dans les journaux:
@IBOutlet weak var singleton: Singleton!
Par conséquent , il existe en effet deux cas différents. J'ai également ajouté ce code ailleurs dans mon délégué d'application:
@objc class Singleton { static let Singleton sharedInstance = Singleton() @objc dynamic var aProperty = false private init() { } }
À un moment donné, cela produit la sortie suivante:
aProperty: [false, Optional (0), true, facultatif (1)] hidden: false
De toute évidence, étant un singleton, toutes les valeurs doivent correspondre, mais singleton
produit une sortie et Singleton. sharedInstance
en produit un autre. Comme on peut le voir, les appels à value (forKey :)
correspondent à leurs objets respectifs, donc KVC ne devrait pas être un problème.
Comment déclarer une classe singleton dans Swift et la câbler avec Interface Builder pour éviter qu'elle ne soit instanciée deux fois?
Si ce n'est pas possible, comment pourrais-je résoudre le problème de la liaison d'une propriété globale à un contrôle dans Interface Builder?
J'espère que la description était suffisamment détaillée, mais si quelqu'un pense qu'un MCVE est nécessaire, laissez un commentaire et je créerai un et téléverser sur GitHub.
3 Réponses :
Malheureusement, vous ne pouvez pas renvoyer une instance différente de init
dans Swift.
Voici quelques solutions de contournement possibles:
init
renvoient toujours une instance de singleton Swift partagée.
Pour être honnête, vous pouvez renvoyer une instance différente de init
dans Swift avec une solution de contournement laide mais autorisée, mais je l'éviterais car ce n'est pas une bonne idée. Et je considérerais cela malheureux. C'est toujours dommage de casser le système de type, même un peu.
Permettez-moi d'ajouter que j'ai utilisé l'option 2 (créer une classe d'assistance à utiliser dans Interface Builder) pour une classe différente, avec beaucoup de propriétés, où je ne voulais pas les lier manuellement selon ma réponse .
Il existe un moyen de contourner le problème dans mon cas particulier.
Rappelez-vous de la question que je voulais uniquement cacher et afficher un menu en fonction de l'état de aProperty
dans ce singleton. Alors que j'essayais d'éviter d'écrire autant de code que possible, en faisant tout dans Interface Builder, il semble que dans ce cas, il soit beaucoup moins compliqué d'écrire simplement la liaison par programme:
menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)
p>
Je veux juste commencer ma réponse en déclarant que les singletons ne doivent pas être utilisés pour partager l'état global. Bien qu'ils puissent sembler plus faciles à utiliser au début, ils ont tendance à générer beaucoup de maux de tête par la suite, car ils peuvent être modifiés pratiquement de n'importe où, rendant parfois votre programme imprévisible.
Cela étant dit, ce n'est pas impossible à réaliser ce dont vous avez besoin, mais avec un peu de cérémonie:
@objc class Singleton: NSObject { // using this class behind the scenes, this is the actual singleton class SingletonStorage: NSObject { @objc dynamic var aProperty = false } private static var storage = SingletonStorage() // making sure all instances use the same storage, regardless how // they were created @objc dynamic var storage = Singleton.storage // we need to tell to KVO which changes in related properties affect // the ones we're interested into override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> { switch key { case "aProperty": return ["storage.aProperty"] default: return super.keyPathsForValuesAffectingValue(forKey: key) } } // and simply convert it to a computed property @objc dynamic var aProperty: Bool { get { return Singleton.storage.aProperty } set { Singleton.storage.aProperty = newValue } } }
La propriété en question vérifie si j'ai pu me connecter à un outil d'assistance, et je pense que c'est une bonne utilisation du modèle singleton. Il n'est mis à jour que par le code à l'intérieur de la classe, il ne peut pas être défini par un autre code. Cela dit, il semble qu'au lieu d'essayer de forcer IB à accepter un singleton, mon autre réponse finit par être la plus claire solution dans ce cas particulier, à mon avis.
Ce serait parfait si vous supprimez tout après la première ligne! ;-) Dans tous les cas, votre classe Singleton
est vraiment mal nommée, car ce n'est pas du tout un singleton. Vous pouvez l'appeler SingletonProxy
ou FauxSingleton
ou quelque chose comme ça.
@Caleb Je suis d'accord, j'ai juste utilisé les mêmes noms que dans la question. BTW, j'aurais pu ajouter une propriété shared
et rendre le init
privé, les laisser à l'extérieur pour ne pas encombrer le code de la solution.
@swineone ne peut pas discuter de la propreté, après tout, la solution que vous avez trouvée n'a qu'une seule ligne;)
Même si vous dites que votre classe est un singleton, en ajoutant un nouvel
Object
au storyboard, vous ne faites pas référence à ce singleton. Vous créez une nouvelle instance . En fait, je suis presque sûr que vous ne pouvez pas faire cela. Vous ne pouvez pas référencer un objet créé dans le code dans vos storyboards.@Sulthan Je pensais que la manière dont la classe était déclarée exclurait cela, car
init ()
étant privé.Interface Builder crée des objets à l'aide d'Objective-C et vous ne pouvez pas vraiment y appliquer un init privé. Même si vous le pouviez, vous obtiendriez juste une erreur lors du chargement du storyboard.
C'est dommage, je n'en avais aucune idée. Pouvez-vous suggérer un design différent dans ce cas? Peut-être que si je rendais
une propriété
statique
?Cela fait quelques années, mais je pense que J'ai piraté des singletons de duplication IB en utilisant
allocWithZone
etcopyWithZone