6 votes

Swift : "L'initialisateur défaillant 'init()' ne peut pas remplacer un initialisateur non défaillant" vs. paramètres par défaut

Si je déclare

public class A: NSObject {
    public class X { }
    public init?(x: X? = nil) { }
}

tout va bien. Lorsqu'on l'utilise comme let a = A() l'initialisateur est appelé comme prévu.

Maintenant, j'aimerais avoir la classe imbriquée X privée, et la version paramétrée de la init (il faut bien sûr qu'il en soit ainsi). Mais un simple init?() doit rester accessible au public comme il l'était auparavant. J'écris donc

public class B: NSObject {
    private class X { }
    private init?(x: X?) { }
    public convenience override init?() { self.init(x: nil) }
}

Mais cela donne lieu à une erreur avec le init?() initialisateur : l'initialisateur défaillant 'init()' ne peut pas remplacer un initialisateur non défaillant avec l'initialisateur surchargé étant le public init() en NSObject .

Comment puis-je déclarer efficacement un initialisateur A.init?() sans le conflit mais pas B.init?() ?

Question bonus : Pourquoi ne suis-je pas autorisé à remplacer un initialisateur non disponible par un initialisateur disponible ? L'inverse est légal : Je peux remplacer un initialisateur défaillant par un initialisateur non défaillant, ce qui nécessite l'utilisation d'une méthode forcée. super.init()! et introduit donc le risque d'une erreur d'exécution. Pour moi, laisser la sous-classe disposer de l'initialisateur défaillant me semble plus raisonnable, car une extension de fonctionnalité introduit plus de risques d'échec. Mais peut-être que quelque chose m'échappe - toute explication sera la bienvenue.

7voto

Stefan Points 513

C'est ainsi que j'ai résolu le problème pour moi :

I peut déclarer

public convenience init?(_: Void) { self.init(x: nil) }

et l'utiliser comme

let b = B(())

ou même

let b = B()

- ce qui est logique puisque sa signature est (en quelque sorte) différente, donc pas de surcharge ici. Seule l'utilisation d'un Void et l'omettre dans l'appel est un peu étrange Mais la fin justifie les moyens, je suppose :-)

2voto

Kametrixom Points 57

Après quelques manipulations, je pense avoir compris. Considérons un protocole nécessitant cet initialisateur et une classe l'implémentant :

protocol I {
    init()
}

class A : I {
    init() {}
}

Cela donne l'erreur suivante : "L'exigence de l'initialisateur 'init()' ne peut être satisfaite que par un required initialisateur dans la classe non finale "A"". C'est logique, car on peut toujours déclarer une sous-classe de A qui n'hérite pas de cet initialisateur :

class B : A {
    // init() is not inherited
    init(n: Int) {}
}

Nous devons donc placer notre initialisateur dans A required :

class A : I {
    required init() {}
}

Maintenant, si nous regardons le NSObject nous pouvons voir que l'initialisateur est no required :

public class NSObject : NSObjectProtocol {
    [...]
    public init()
    [...]
}

Nous pouvons le confirmer en le sous-classant, en ajoutant un initialisateur différent et en essayant d'utiliser l'initialisateur normal :

class MyObject : NSObject {
    init(n: Int) {}
}

MyObject() // Error: Missing argument for parameter 'n:' in call

C'est là qu'intervient la chose la plus étrange : nous peut étendre NSObject pour se conformer à la I même s'il ne nécessite pas cet initialisateur :

extension NSObject : I {} // No error (!)

Je pense honnêtement qu'il s'agit soit d'un bug, soit d'une exigence pour que l'interopération ObjC fonctionne (EDIT : C'est un bug et il a déjà été corrigé dans la dernière version). Cette erreur ne devrait pas être possible :

extension I {
    static func get() -> Self { return Self() }
}

MyObject.get()
// Runtime error: use of unimplemented initializer 'init()' for class '__lldb_expr_248.MyObject'

Maintenant, pour répondre à votre question :

Dans votre deuxième exemple de code, le compilateur a raison de dire que vous ne pouvez pas remplacer un initialisateur non disponible par un initialisateur disponible.

Dans le premier cas, vous ne surchargez pas réellement l'initialisateur (pas de override ), mais en déclarant un nouveau par lequel l'autre ne peut pas être hérité.

Maintenant que j'ai écrit tout cela, je ne suis même pas sûr que la première partie de ma réponse ait un rapport avec votre question, mais c'est quand même bien de trouver un bogue.

Je vous suggère de faire ceci à la place :

public convenience override init() { self.init(x: nil)! }

Jetez également un coup d'œil sur le site Initialization section de la référence Swift.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X