Le risposte esistenti raccontano solo metà della convenience
storia. L'altra metà della storia, la metà che nessuna delle risposte esistenti copre, risponde alla domanda che Desmond ha pubblicato nei commenti:
Perché Swift mi costringerebbe a mettere convenience
davanti al mio inizializzatore solo perché ho bisogno di chiamarlo self.init
? `
L'ho toccato leggermente in questa risposta , in cui tratterò dettagliatamente alcune delle regole di inizializzazione di Swift, ma il focus principale era sulla required
parola. Ma quella risposta stava ancora affrontando qualcosa che è rilevante per questa domanda e questa risposta. Dobbiamo capire come funziona l'ereditarietà dell'inizializzatore Swift.
Poiché Swift non consente variabili non inizializzate, non è garantito che erediti tutti (o alcuno) gli inizializzatori dalla classe da cui erediti. Se eseguiamo la sottoclasse e aggiungiamo variabili di istanza non inizializzate alla nostra sottoclasse, abbiamo smesso di ereditare gli inizializzatori. E finché non aggiungiamo i nostri inizializzatori, il compilatore ci urlerà contro.
Per essere chiari, una variabile di istanza non inizializzata è una qualsiasi variabile di istanza a cui non viene assegnato un valore predefinito (tenendo presente che gli opzionali e quelli implicitamente non scartati assumono automaticamente un valore predefinito di nil
).
Quindi in questo caso:
class Foo {
var a: Int
}
a
è una variabile di istanza non inizializzata. Questo non verrà compilato a meno che non diamo a
un valore predefinito:
class Foo {
var a: Int = 0
}
o inizializza a
con un metodo di inizializzazione:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Ora, vediamo cosa succede se effettuiamo la sottoclasse Foo
, vero?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
Destra? Abbiamo aggiunto una variabile e aggiunto un inizializzatore per impostare un valore in b
modo che venga compilato. A seconda di quale lingua si sta venendo da, ci si potrebbe aspettare che Bar
ha ereditato Foo
's inizializzatore, init(a: Int)
. Ma non lo fa. E come potrebbe? Come fa Foo
's init(a: Int)
know how per assegnare un valore alla b
variabile che Bar
ha aggiunto? Non Quindi non possiamo inizializzare aBar
un'istanza con un inizializzatore che non può inizializzare tutti i nostri valori.
Cosa c'entra tutto questo convenience
?
Bene, diamo un'occhiata alle regole sull'ereditarietà dell'inizializzatore :
Regola 1
Se la sottoclasse non definisce alcun inizializzatore designato, eredita automaticamente tutti i suoi inizializzatori designati superclasse.
Regola 2
Se la sottoclasse fornisce un'implementazione di tutti i suoi inizializzatori designati della superclasse, ereditandoli secondo la regola 1 o fornendo un'implementazione personalizzata come parte della sua definizione, eredita automaticamente tutti gli inizializzatori di convenienza della superclasse.
Nota la Regola 2, che menziona gli inizializzatori di convenienza.
Così che la convenience
parola chiave non indicare a noi che inizializzatori possono essere ereditate dalle sottoclassi che le variabili di istanza aggiuntivo senza valori di default.
Prendiamo questa Base
classe di esempio :
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
Si noti che ne abbiamo tre convenience
inizializzatori qui. Ciò significa che abbiamo tre inizializzatori che possono essere ereditati. E abbiamo un inizializzatore designato (un inizializzatore designato è semplicemente un inizializzatore che non è un inizializzatore di convenienza).
Possiamo istanziare istanze della classe base in quattro modi diversi:
Quindi, creiamo una sottoclasse.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Stiamo ereditando da Base
. Abbiamo aggiunto la nostra variabile di istanza e non gli abbiamo dato un valore predefinito, quindi dobbiamo aggiungere i nostri inizializzatori. Abbiamo aggiunto uno, init(a: Int, b: Int, c: Int)
ma non corrisponde alla firma del Base
codice categoria è designato inizializzatore: init(a: Int, b: Int)
. Ciò significa che non stiamo ereditando alcun inizializzatore da Base
:
Quindi, cosa accadrebbe se ereditassimo Base
, ma andassimo avanti e implementassimo un inizializzatore che corrispondeva all'inizializzatore designato Base
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
Ora, oltre ai due inizializzatori che abbiamo implementato direttamente in questa classe, poiché abbiamo implementato un inizializzatore che Base
identifica l'inizializzatore designato della classe, possiamo ereditare tutti Base
gli convenience
inizializzatori della classe :
Il fatto che l'inizializzatore con la firma corrispondente sia contrassegnato come convenience
qui non fa differenza. Significa solo che Inheritor
ha un solo inizializzatore designato. Quindi, se ereditiamo Inheritor
, dovremmo solo implementare quell'inizializzatore designato e quindi erediteremoInheritor
l'inizializzatore di convenienza, il che a sua volta significa che abbiamo implementato tutti gli Base
inizializzatori designati e possiamo ereditare i suoi convenience
inizializzatori.