web-dev-qa-db-fra.com

Erreur dans la classe Swift: propriété non initialisée à l'appel de super.init

J'ai deux classes, Shape et Square

class Shape {
    var numberOfSides = 0
    var name: String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Avec l'implémentation ci-dessus, j'obtiens l'erreur:

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

Pourquoi dois-je définir self.sideLength avant d'appeler super.init?

191
JuJoDi

Citez le langage de programmation Swift, qui répond à votre question:

"Le compilateur de Swift effectue quatre contrôles de sécurité utiles pour s'assurer que l'initialisation en deux phases est terminée sans erreur:"

Contrôle de sécurité 1 "Un initialiseur désigné doit s'assurer que toutes les" propriétés introduites par sa classe sont initialisées avant de déléguer un initialiseur de superclasse ".

Extrait de: Apple Inc. "Le langage de programmation Swift." IBooks. https://iTunes.Apple.com/us/book/Swift-programming-language/id881256329?mt=11

160
Ruben

Swift a une séquence d'opérations très claire et spécifique qui est effectuée dans les initialiseurs. Commençons par quelques exemples de base et abordons un cas général.

Prenons un objet A. Nous le définirons comme suit.

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

Notez que A n’a pas de super-classe, il ne peut donc pas appeler une fonction super.init () car elle n’existe pas.

OK, passons maintenant à la sous-classe A avec une nouvelle classe nommée B.

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

Ceci est différent de Objective-C où [super init] serait généralement appelé avant tout le reste. Pas si à Swift. Vous devez vous assurer que vos variables d'instance sont dans un état cohérent avant de faire quoi que ce soit, y compris les méthodes d'appel (qui incluent l'initialiseur de votre superclasse).

90
Hitendra Solanki

Le "super.init ()" doit être appelé après l’initialisation de toutes vos variables d’instance.

Dans la vidéo "Intermediate Swift" d’Apple (vous pouvez la trouver dans la page de ressources vidéo pour développeurs Apple https://developer.Apple.com/videos/wwdc/2014/ ), à l'adresse vers 28:40, il est dit explicitement que tous les initialiseurs de la super classe doivent être appelés APRÈS l’initialisation de vos variables d’instance.

En Objective-C, c'était l'inverse. Dans Swift, étant donné que toutes les propriétés doivent être initialisées avant leur utilisation, nous devons d’abord les initialiser. Ceci est destiné à empêcher un appel à une fonction remplacée par la méthode "init ()" de la super classe, sans initialiser les propriétés au préalable.

Donc, la mise en œuvre de "Square" devrait être:

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        numberOfSides = 4
        super.init(name:name) // Correct position for "super.init()"
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}
34
Shuyang

De la docs

Contrôle de sécurité 1

Un initialiseur désigné doit s'assurer que toutes les propriétés introduites par sa classe sont initialisées avant de déléguer un initialiseur de superclasse.


Pourquoi avons-nous besoin d'une vérification de sécurité comme celle-ci?

Pour répondre à cette question, passons par le processus d’initialisation dans Swift.

Initialisation en deux phases

L'initialisation de la classe dans Swift est un processus en deux phases. Dans la première phase, chaque classe stockée se voit attribuer une valeur initiale par la classe qui l'a introduite. Une fois que l'état initial de chaque propriété stockée a été déterminé, la deuxième phase commence et chaque classe se voit offrir la possibilité de personnaliser davantage ses propriétés stockées avant que la nouvelle instance ne soit considérée comme prête à être utilisée.

L'utilisation d'un processus d'initialisation en deux phases sécurise l'initialisation, tout en offrant une flexibilité complète à chaque classe dans une hiérarchie de classes. L'initialisation en deux phases empêche l'accès aux valeurs de propriété avant leur initialisation et empêche les valeurs de propriété d'être définies de manière inattendue par une autre valeur par un autre initialiseur.

Donc, pour s’assurer que le processus d’initialisation en deux étapes est effectué comme défini ci-dessus, il existe quatre contrôles de sécurité, dont l’un est:

Contrôle de sécurité 1

Un initialiseur désigné doit s'assurer que toutes les propriétés introduites par sa classe sont initialisées avant de déléguer un initialiseur de superclasse.

Maintenant, l'initialisation en deux phases ne parle jamais d'ordre, mais ce contrôle de sécurité introduit le super.init à commander, après l'initialisation de toutes les propriétés.

Le contrôle de sécurité 1 peut sembler sans importance, car L’initialisation en deux phases empêche l’accès aux valeurs de propriété avant leur initialisation , sans ce contrôle de sécurité 1.

Comme dans cet exemple

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        super.init(sides: 3, named: "Triangle") 
        self.hypotenuse = hypotenuse
    }
}

Triangle.init a initialisé toutes les propriétés avant leur utilisation. Donc, le contrôle de sécurité 1 semble hors de propos,

Mais alors il pourrait y avoir un autre scénario, un peu complexe,

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
        printShapeDescription()
    }
    func printShapeDescription() {
        print("Shape Name :\(self.name)")
        print("Sides :\(self.sides)")
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        self.hypotenuse = hypotenuse
        super.init(sides: 3, named: "Triangle")
    }

    override func printShapeDescription() {
        super.printShapeDescription()
        print("Hypotenuse :\(self.hypotenuse)")
    }
}

let triangle = Triangle(hypotenuse: 12)

Sortie:

Shape Name :Triangle
Sides :3
Hypotenuse :12

Ici, si nous avions appelé le super.init avant de définir le hypotenuse, l'appel super.init aurait alors appelé le printShapeDescription() et, cette opération ayant été annulée, il serait tout d'abord renvoyé à la classe Triangle implémentation de printShapeDescription(). La printShapeDescription() de la classe Triangle accède à la hypotenuse une propriété non facultative qui n'a toujours pas été initialisée. Et cela n'est pas autorisé car L'initialisation en deux phases empêche l'accès aux valeurs de propriété avant leur initialisation

Assurez-vous donc que l'initialisation en deux phases est effectuée telle que définie, qu'il doit exister un ordre spécifique pour appeler super.init, c'est-à-dire qu'après avoir initialisé toutes les propriétés introduites par la classe self, nous avons donc besoin d'un Contrôle de sécurité 1

30
BangOperator

Désolé pour le formatage laid. Il suffit de poser un caractère question après déclaration et tout ira bien. Une question indique au compilateur que la valeur est facultative.

class Square: Shape {
    var sideLength: Double?   // <=== like this ..

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit1:

Il existe un meilleur moyen de passer cette erreur. Selon le commentaire de jmaschad, il n'y a aucune raison d'utiliser optionnel dans votre cas car les optionnels ne sont pas confortables et vous devez toujours vérifier si optionnel n'est pas nul avant d'y accéder. Donc, tout ce que vous avez à faire est d’initialiser le membre après la déclaration:

class Square: Shape {
    var sideLength: Double=Double()   

    init(sideLength:Double, name:String) {
        super.init(name:name)
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit2:

Après deux points négatifs sur cette réponse, j'ai trouvé un moyen encore meilleur. Si vous voulez que le membre de la classe soit initialisé dans votre constructeur, vous devez lui attribuer une valeur initiale à l'intérieur du constructeur et avant l'appel de super.init (). Comme ça:

class Square: Shape {
    var sideLength: Double  

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength   // <= before super.init call..
        super.init(name:name)
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Bonne chance pour apprendre Swift.

14
fnc12

Swift vous oblige à initialiser chaque membre var avant qu'il ne soit/ne sera jamais utilisé. Puisqu'il ne peut pas être sûr de ce qui se passe quand il est super tourner, il se trompe: mieux vaut prévenir que guérir

9
Daij-Djan

Edward,

Vous pouvez modifier le code dans votre exemple comme ceci:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

Ceci utilise un facultatif implicitement non emballé.

Dans la documentation, on peut lire:

"Comme pour les options, si vous ne fournissez pas de valeur initiale lorsque vous déclarez une variable ou une propriété facultative implicitement non enveloppée, sa valeur par défaut est automatiquement nil."

7
Pavel Gubarev

Swift ne vous permettra pas d’initialiser la super classe sans l’initialisation des propriétés, l’inverse de Obj C. Vous devez donc initialiser toutes les propriétés avant d’appeler "super.init".

Veuillez vous rendre sur http://blog.scottlogic.com/2014/11/20/Swift-initialisation.html . Cela donne une bonne explication à votre problème.

6
jishnu bala

Ajoutez nil à la fin de la déclaration.


// Must be nil or Swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

Cela a fonctionné pour mon cas, mais peut ne pas fonctionner pour le vôtre

5
Michael

Vous commencez juste dans le mauvais ordre.

     class Shape2 {
        var numberOfSides = 0
        var name: String
        init(name:String) {
            self.name = name
        }
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
    }

    class Square2: Shape2 {
        var sideLength: Double

        init(sideLength:Double, name:String) {

            self.sideLength = sideLength
            super.init(name:name) // It should be behind "self.sideLength = sideLength"
            numberOfSides = 4
        }
        func area () -> Double {
            return sideLength * sideLength
        }
    }
1
ylgwhyh