Perché la parola chiave convenience è addirittura necessaria in Swift?

Poiché Swift supporta il metodo e l’overload di inizializzatore, puoi mettere più init uno accanto all’altro e utilizzare quello che ritieni conveniente:

 class Person { var name:String init(name: String) { self.name = name } init() { self.name = "John" } } 

Quindi perché esiste anche una parola chiave di convenience ? Cosa rende sostanzialmente migliore la seguente?

 class Person { var name:String init(name: String) { self.name = name } convenience init() { self.init(name: "John") } } 

Le risposte esistenti dicono solo metà della storia della convenience . L’altra metà della storia, la metà che nessuna delle risposte esistenti copre, risponde alla domanda che Desmond ha pubblicato nei commenti:

Perché Swift dovrebbe costringermi a mettere la convenience di fronte al mio inizializzatore solo perché ho bisogno di chiamare self.init da esso? ”

L’ho leggermente toccato in questa risposta , in cui riporto molte delle regole di inizializzazione di Swift nei dettagli, ma l’objective principale era la parola required . 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 nessuno) gli inizializzatori dalla class da cui si eredita. Se eseguiamo la sottoclass e aggiungiamo qualsiasi variabile di istanza non inizializzata alla nostra sottoclass, abbiamo smesso di ereditare gli inizializzatori. E fino a quando non aggiungeremo gli inizializzatori, il compilatore ci urlerà contro.

Per essere chiari, una variabile di istanza non inizializzata è una variabile di istanza a cui non viene assegnato un valore predefinito (tenendo presente che gli optionals e gli optionals implicitamente non utilizzati 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 forniamo un valore predefinito:

 class Foo { var a: Int = 0 } 

o inizializzare a in un metodo di inizializzazione:

 class Foo { var a: Int init(a: Int) { self.a = a } } 

Ora, vediamo cosa succede se riusciamo a sottoclass 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 abbiamo aggiunto un inizializzatore per impostare un valore su b modo che venga compilato. A seconda della lingua da cui vieni, potresti aspettarti che Bar abbia ereditato l’inizializzatore di Foo , init(a: Int) . Ma non è così. E come potrebbe? In che modo init(a: Int) Foo sa come assegnare un valore alla variabile b aggiunta da Bar ? Non è così. Quindi non possiamo inizializzare un’istanza Bar con un inizializzatore che non può inizializzare tutti i nostri valori.

Cosa c’entra tutto questo con la convenience ?

Bene, diamo un’occhiata alle regole sull’ereditarietà dell’inizializzatore :

Regola 1

Se la sottoclass non definisce alcun inizializzatore designato, eredita automaticamente tutti gli inizializzatori designati della superclass.

Regola 2

Se la sottoclass fornisce un’implementazione di tutti gli inizializzatori designati della superclass, ereditandoli come da regola 1 o fornendo un’implementazione personalizzata come parte della sua definizione, eredita automaticamente tutti gli inizializzatori di convenienza della superclass.

Si noti la regola 2, che menziona gli inizializzatori di convenienza.

Quindi, ciò che fa la parola chiave convenience è indicarci quali inizializzatori possono essere ereditati da sottoclassi che aggiungono variabili di istanza senza valori predefiniti.

Prendiamo questo esempio Base class Base :

 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 qui abbiamo tre inizializzatori di convenience . 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 class base in quattro modi diversi:

inserisci la descrizione dell'immagine qui

Quindi, creiamo una sottoclass.

 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 assegnato un valore predefinito, quindi dobbiamo aggiungere i nostri inizializzatori. Abbiamo aggiunto uno, init(a: Int, b: Int, c: Int) , ma non corrisponde alla firma dell’inizializzatore designato della class Base : init(a: Int, b: Int) . Ciò significa che non stiamo ereditando alcun inizializzatore da Base :

inserisci la descrizione dell'immagine qui

Quindi, cosa succederebbe se ereditassimo da Base , ma siamo andati avanti e abbiamo implementato un inizializzatore che corrispondeva all’inizializzatore designato da 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 class, poiché abbiamo implementato un inizializzatore che Base l’inizializzatore designato della class Base , possiamo ereditare tutti gli inizializzatori di convenience della class Base :

inserisci la descrizione dell'immagine qui

Il fatto che l’inizializzatore con la firma corrispondente sia contrassegnato come convenience non fa differenza qui. Significa solo che Inheritor ha solo un inizializzatore designato. Quindi, se ereditiamo da Inheritor , dovremmo semplicemente implementare quell’inizializzatore designato, e quindi erediteremo l’inizializzatore di convenienza di Inheritor , che a sua volta significa che abbiamo implementato tutti gli inizializzatori designati di Base e possiamo ereditare il suo iniziatori di convenience .

Per lo più chiarezza. Dal secondo esempio,

 init(name: String) { self.name = name } 

è richiesto o designato . Deve inizializzare tutte le costanti e le variabili. Gli inizializzatori di convenienza sono facoltativi e in genere possono essere utilizzati per semplificare l’inizializzazione. Ad esempio, supponi che la tua class Person abbia una variabile opzionale gender:

 var gender: Gender? 

dove il genere è un enum

 enum Gender { case Male, Female } 

potresti avere degli inizializzatori convenienti come questo

 convenience init(maleWithName: String) { self.init(name: name) gender = .Male } convenience init(femaleWithName: String) { self.init(name: name) gender = .Female } 

Gli inizializzatori di convenienza devono chiamare gli inizializzatori designati o richiesti in essi. Se la tua class è una sottoclass, deve chiamare super.init() all’interno dell’inizializzazione.

Bene, per prima cosa mi viene in mente che è usato nell’ereditarietà delle classi per l’organizzazione del codice e la leggibilità. Continuando con la tua class Person , pensa a uno scenario come questo

 class Person{ var name: String init(name: String){ self.name = name } convenience init(){ self.init(name: "Unknown") } } class Employee: Person{ var salary: Double init(name:String, salary:Double){ self.salary = salary super.init(name: name) } override convenience init(name: String) { self.init(name:name, salary: 0) } } let employee1 = Employee() // {{name "Unknown"} salary 0} let john = Employee(name: "John") // {{name "John"} salary 0} let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700} 

Con l’inizializzatore di convenienza sono in grado di creare un object Employee() senza valore, quindi la parola convenience

A parte i punti che altri utenti hanno spiegato qui è il mio punto di vista.

Sento fortemente la connessione tra l’inizializzatore di convenienza e le estensioni. Per quanto mi riguarda, gli inizializzatori di convenienza sono molto utili quando voglio modificare (nella maggior parte dei casi renderlo breve o facile) l’inizializzazione di una class esistente.

Ad esempio, alcune classi di terze parti che usi hanno init con quattro parametri, ma nella tua applicazione gli ultimi due hanno lo stesso valore. Per evitare ulteriori digitazioni e rendere il tuo codice pulito, potresti definire self.init convenience init con solo due parametri e al suo interno chiamare self.init con i parametri con valori di default.

Secondo la documentazione di Swift 2.1 , gli inizializzatori di convenience devono rispettare alcune regole specifiche:

  1. Un inizializzatore di convenience può solo chiamare gli inizializzatori nella stessa class, non in super classi (solo attraverso, non sopra)

  2. Un inizializzatore convenience deve chiamare un inizializzatore designato da qualche parte nella catena

  3. Un inizializzatore di convenience non può modificare QUALSIASI proprietà prima che abbia chiamato un altro inizializzatore, mentre un inizializzatore designato deve inizializzare le proprietà introdotte dalla class corrente prima di chiamare un altro inizializzatore.

Usando la parola chiave convenience , il compilatore Swift sa che deve controllare queste condizioni, altrimenti non potrebbe.

Una class può avere più di un inizializzatore designato. Un inizializzatore di convenienza è un inizializzatore secondario che deve chiamare un inizializzatore designato della stessa class.