Meilleure pratique pour implémenter un initialiseur disponible dans Swift

Avec le code suivant, j’essaie de définir une classe de modèle simple et son initialiseur disponible, qui prend un dictionnaire (json-) comme paramètre. L’initialiseur doit renvoyer nil si le nom d’utilisateur n’est pas défini dans json d’origine.

1. Pourquoi le code ne comstack-t-il pas? Le message d’erreur indique:

Toutes les propriétés stockées d’une instance de classe doivent être initialisées avant de renvoyer zéro à un initialiseur.

Cela n’a pas de sens. Pourquoi devrais-je initialiser ces propriétés lorsque je prévois de retourner nil ?

2. Mon approche est-elle la bonne ou y a-t-il d’autres idées ou des schémas communs pour atteindre mon objective?

 class User: NSObject { let userName: Ssortingng let isSuperUser: Bool = false let someDetails: [Ssortingng]? init?(dictionary: NSDictionary) { if let value: Ssortingng = dictionary["user_name"] as? Ssortingng { userName = value } else { return nil } if let value: Bool = dictionary["super_user"] as? Bool { isSuperUser = value } someDetails = dictionary["some_details"] as? Array super.init() } } 

Mise à jour: du journal des changements Swift 2.2 (publié le 21 mars 2016):

Les initialiseurs de classe désignés déclarés comme disponibles ou lancés peuvent maintenant renvoyer nil ou générer une erreur, respectivement, avant que l’object soit complètement initialisé.


Pour Swift 2.1 et versions antérieures:

Selon la documentation d’Apple (et votre erreur de compilation), une classe doit initialiser toutes ses propriétés stockées avant de renvoyer nil partir d’un initialiseur disponible:

Pour les classes, toutefois, un initialiseur disponible peut déclencher un échec d’initialisation uniquement après que toutes les propriétés stockées introduites par cette classe ont été définies sur une valeur initiale et que toute délégation d’initialisation a eu lieu.

Remarque: Cela fonctionne bien pour les structures et les énumérations, mais pas pour les classes.

La manière suggérée de gérer les propriétés stockées qui ne peuvent pas être initialisées avant que l’initialiseur échoue consiste à les déclarer comme des options optionnelles non déballées.

Exemple des documents:

 class Product { let name: Ssortingng! init?(name: Ssortingng) { if name.isEmpty { return nil } self.name = name } } 

Dans l’exemple ci-dessus, la propriété name de la classe Product est définie comme ayant un type de chaîne facultatif implicitement déballé (Ssortingng!). Comme il s’agit d’un type facultatif, cela signifie que la propriété name a une valeur par défaut nulle avant de recevoir une valeur spécifique lors de l’initialisation. Cette valeur par défaut de nil signifie que toutes les propriétés introduites par la classe Product ont une valeur initiale valide. Par conséquent, l’initialiseur disponible pour Product peut déclencher un échec d’initialisation au début de l’initialiseur s’il reçoit une chaîne vide avant d’atsortingbuer une valeur spécifique à la propriété name de l’initialiseur.

Dans votre cas, cependant, il suffit de définir userName tant que Ssortingng! ne corrige pas l’erreur de compilation car vous devez vous soucier de l’initialisation des propriétés de votre classe de base, NSObject . Heureusement, avec userName défini comme une Ssortingng! , vous pouvez en fait appeler super.init() avant de return nil qui initialisera votre classe de base NSObject et corrigera l’erreur de compilation.

 class User: NSObject { let userName: Ssortingng! let isSuperUser: Bool = false let someDetails: [Ssortingng]? init?(dictionary: NSDictionary) { super.init() if let value = dictionary["user_name"] as? Ssortingng { self.userName = value } else { return nil } if let value: Bool = dictionary["super_user"] as? Bool { self.isSuperUser = value } self.someDetails = dictionary["some_details"] as? Array } } 

Cela n’a pas de sens. Pourquoi devrais-je initialiser ces propriétés lorsque je prévois de retourner zéro?

Selon Chris Lattner, c’est un bug. Voici ce qu’il dit:

Ceci est une limitation d’implémentation dans le compilateur swift 1.1, documentée dans les notes de publication. Le compilateur est actuellement incapable de détruire les classes partiellement initialisées dans tous les cas, donc il ne permet pas de créer une situation où il devrait être. Nous considérons qu’il s’agit d’un bogue à corriger dans les futures versions, et non une fonctionnalité.

La source

MODIFIER:

Donc swift est maintenant open source et selon ce changelog il est maintenant corrigé dans des instantanés de swift 2.2

Les initialiseurs de classe désignés déclarés comme disponibles ou lancés peuvent maintenant renvoyer nil ou générer une erreur, respectivement, avant que l’object soit complètement initialisé.

J’accepte que la réponse de Mike S soit la recommandation d’Apple, mais je ne pense pas que ce soit la meilleure pratique. L’intérêt d’un système de type fort est de déplacer les erreurs d’exécution pour comstackr le temps. Cette “solution” va à l’encontre de cet objective. À mon humble avis, mieux serait d’aller de l’avant et d’initialiser le nom d’utilisateur à "" , puis vérifiez-le après le super.init (). Si les noms d’utilisateur sont autorisés, définissez un indicateur.

 class User: NSObject { let userName: Ssortingng = "" let isSuperUser: Bool = false let someDetails: [Ssortingng]? init?(dictionary: [Ssortingng: AnyObject]) { if let user_name = dictionary["user_name"] as? Ssortingng { userName = user_name } if let value: Bool = dictionary["super_user"] as? Bool { isSuperUser = value } someDetails = dictionary["some_details"] as? Array super.init() if userName.isEmpty { return nil } } } 

Une autre façon de contourner la limitation consiste à travailler avec une classe-fonctions pour effectuer l’initialisation. Vous pourriez même vouloir déplacer cette fonction vers une extension:

 class User: NSObject { let username: Ssortingng let isSuperUser: Bool let someDetails: [Ssortingng]? init(userName: Ssortingng, isSuperUser: Bool, someDetails: [Ssortingng]?) { self.userName = userName self.isSuperUser = isSuperUser self.someDetails = someDetails super.init() } } extension User { class func fromDictionary(dictionary: NSDictionary) -> User? { if let username: Ssortingng = dictionary["user_name"] as? Ssortingng { let isSuperUser = (dictionary["super_user"] as? Bool) ?? false let someDetails = dictionary["some_details"] as? [Ssortingng] return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails) } return nil } } 

L’utiliser deviendrait:

 if let user = User.fromDictionary(someDict) { // Party hard } 

Bien que Swift 2.2 soit sorti et que vous n’ayez plus besoin d’initialiser complètement l’object avant d’initialiser l’initialiseur, vous devez tenir vos chevaux jusqu’à ce que https://bugs.swift.org/browse/SR-704 soit corrigé.

J’ai découvert que cela pouvait être fait dans Swift 1.2

Il y a des conditions:

  • Les propriétés requirejses doivent être déclarées comme des options
  • Atsortingbuez une valeur à vos propriétés requirejses exactement une fois. Cette valeur peut être nulle.
  • Ensuite, appelez super.init () si votre classe hérite d’une autre classe.
  • Après avoir atsortingbué une valeur à toutes les propriétés requirejses, vérifiez si leur valeur est conforme aux attentes. Si non, retournez zéro.

Exemple:

 class ClassName: NSObject { let property: Ssortingng! init?(propertyValue: Ssortingng?) { self.property = propertyValue super.init() if self.property == nil { return nil } } } 

Un initialiseur disponible pour un type de valeur (c’est-à-dire une structure ou une énumération) peut déclencher un échec d’initialisation à tout moment de l’implémentation de son initialiseur

Pour les classes, toutefois, un initialiseur disponible peut déclencher un échec d’initialisation uniquement après que toutes les propriétés stockées introduites par cette classe ont été définies sur une valeur initiale et que toute délégation d’initialisation a eu lieu.

Extrait de: Apple Inc. « Le langage de programmation rapide. ”IBooks. https://itun.es/sg/jEUH0.l

Vous pouvez utiliser l’ initialisation de commodité :

 class User: NSObject { let userName: Ssortingng let isSuperUser: Bool = false let someDetails: [Ssortingng]? init(userName: Ssortingng, isSuperUser: Bool, someDetails: [Ssortingng]?) { self.userName = userName self.isSuperUser = isSuperUser self.someDetails = someDetails } convenience init? (dict: NSDictionary) { guard let userName = dictionary["user_name"] as? Ssortingng else { return nil } guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil } guard let someDetails = dictionary["some_details"] as? [Ssortingng] else { return nil } self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails) } }