Swift From Scratch inizializzazione e inizializzazione della delega

Nella lezione precedente di Swift From Scratch, abbiamo creato un'applicazione funzionale da fare. Il modello di dati potrebbe usare un po 'di amore, però. In questa ultima lezione, andremo a rifattorizzare il modello dati implementando una classe modello personalizzata.

1. Il modello dei dati

Il modello di dati che stiamo per implementare include due classi, a Compito classe e a Fare classe che eredita dal Compito classe. Mentre creiamo e implementiamo queste classi modello, continueremo la nostra esplorazione della programmazione orientata agli oggetti in Swift. In questa lezione, eseguiremo uno zoom sull'inizializzazione delle istanze di classe e su quale eredità di ruolo viene riprodotta durante l'inizializzazione.

Il Compito Classe

Iniziamo con l'implementazione di Compito classe. Crea un nuovo file Swift selezionando Nuovo> File ... da Xcode's File menu. Scegliere Swift File dal iOS> Origine sezione. Nominare il file Task.swift e colpisci Creare.

L'implementazione di base è breve e semplice. Il Compito la classe eredita da NSObject, definito nel Fondazione quadro e ha una proprietà variabile nome di tipo Stringa. La classe definisce due inizializzatori, dentro() e init (nome :). Ci sono alcuni dettagli che potrebbero farti inciampare, quindi lascia che ti spieghi cosa sta succedendo.

import Foundation class Task: NSObject var name: String convenience override init () self.init (nome: "Nuova attività") init (nome: String) self.name = name

Perché il dentro() il metodo è anche definito nel NSObject classe, abbiamo bisogno di prefisso l'inizializzatore con il oltrepassare parola chiave. Abbiamo trattato i metodi di override in precedenza in questa serie. Nel dentro() metodo, invochiamo il init (nome :) metodo, passando dentro "Nuovo compito" come valore per il nome parametro.

Il init (nome :) il metodo è un altro inizializzatore, che accetta un singolo parametro nome di tipo Stringa. In questo inizializzatore, il valore di nome parametro è assegnato al nome proprietà. Questo è abbastanza facile da capire. Destra?

Inizializzatori designati e pratici

Cosa c'è nel convenienza parola chiave che fissa il dentro() metodo? Le classi possono avere due tipi di inizializzatori, designato inizializzatori e convenienza inizializzatori. Gli inizializzatori di convenienza sono preceduti dal prefisso convenienza parola chiave, che implica questo init (nome :) è un inizializzatore designato. Perché? Qual è la differenza tra inizializzatori designati e convenienza?

Inizializzatori designati inizializzare completamente un'istanza di una classe, il che significa che ogni proprietà dell'istanza ha un valore iniziale dopo l'inizializzazione. Guardando il Compito classe, per esempio, vediamo che il nome la proprietà è impostata con il valore di nome parametro del init (nome :) initializer. Il risultato dopo l'inizializzazione è completamente inizializzato Compito esempio.

Iniziatori di convenienza, tuttavia, fare affidamento su un inizializzatore designato per creare un'istanza completamente inizializzata della classe. Ecco perché dentro() inizializzatore del Compito la classe invoca il init (nome :) inizializzatore nella sua implementazione. Questo è indicato come delegazione di inizializzazione. Il dentro() l'inizializzatore delega l'inizializzazione a un inizializzatore designato per creare un'istanza completamente inizializzata del file Compito classe.

Gli inizializzatori di convenienza sono opzionali. Non tutte le classi hanno un iniziatore di convenienza. Sono richiesti inizializzatori designati e una classe deve avere almeno un inizializzatore designato per creare un'istanza completamente inizializzata di se stessa.

Il NSCoding Protocollo

L'implementazione del Compito la lezione non è completa, però. Più avanti in questa lezione, scriveremo una serie di Fare istanze su disco. Questo è possibile solo se istanze del Fare la classe può essere codificata e decodificata.

Non preoccuparti, però, questa non è scienza missilistica. Abbiamo solo bisogno di fare il Compito e Fare classi conformi al NSCoding protocollo. Ecco perché Compito la classe eredita dal NSObject classe dal NSCoding il protocollo può essere implementato solo dalle classi che ereditano, direttamente o indirettamente NSObject. Come il NSObject classe, il NSCoding il protocollo è definito nel Fondazione struttura.

Adottare un protocollo è qualcosa che abbiamo già trattato in questa serie, ma ci sono alcuni trucchi che voglio sottolineare. Iniziamo dicendo al compilatore che il Compito classe conforme al NSCoding protocollo.

import Foundation class Task: NSObject, NSCoding var name: String ...

Successivamente, dobbiamo implementare i due metodi dichiarati nel NSCoding protocollo, init? (coder :) e encode (con :). L'implementazione è semplice se hai familiarità con il NSCoding protocollo.

import Foundation class Task: NSObject, NSCoding var name: String @objc richiesto init? (coder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "name") as! String @objc func encode (con aCoder: NSCoder) aCoder.encode (name, forKey: "name") override di comodità init () self.init (nome: "New Task") init (nome: String) self.name = nome

Il init? (coder :) l'inizializzatore è un inizializzatore designato che inizializza a Compito esempio. Anche se implementiamo il init? (coder :) metodo per conformarsi al NSCoding protocollo, non avrai mai bisogno di invocare direttamente questo metodo. Lo stesso vale per encode (con :), che codifica un'istanza di Compito classe.

Il necessario parola chiave che fissa il init? (coder :) metodo indica che ogni sottoclasse di Compito la classe ha bisogno di implementare questo metodo. Il necessario la parola chiave si applica solo agli inizializzatori, motivo per cui non è necessario aggiungerla al file encode (con :) metodo.

Prima di andare avanti, dobbiamo parlare del @objc attributo. Perché il NSCoding il protocollo è un protocollo Objective-C, conformità del protocollo può essere controllato solo aggiungendo il @objc attributo. In Swift non esiste la conformità del protocollo oi metodi di protocollo opzionali. In altre parole, se una classe aderisce ad un particolare protocollo, il compilatore verifica e si aspetta che ogni metodo del protocollo sia implementato.

Il Fare Classe

Con il Compito classe implementata, è il momento di implementare il Fare classe. Crea un nuovo file Swift e chiamalo ToDo.swift. Diamo un'occhiata all'implementazione del Fare classe.

import Foundation class ToDo: Task var done: Bool @objc richiesto init? (coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") super.init (coder: aDecoder) @objc override func encode (con aCoder: NSCoder) aCoder.encode (done, forKey: "done") super.encode (con: aCoder) init (nome: String, done: Bool) self.done = done super.init (nome : nome)  

Il Fare la classe eredita dal Compito classe e dichiara una proprietà variabile fatto di tipo Bool. Oltre ai due metodi richiesti di NSCoding protocollo che eredita dal Compito classe, dichiara anche un inizializzatore designato, init (nome: fatto :).

Come in Objective-C, il super parola chiave si riferisce alla superclasse, il Compito classe in questo esempio. C'è un dettaglio importante che merita attenzione. Prima di invocare il init (nome :) metodo sulla superclasse, ogni proprietà dichiarata dal Fare la classe deve essere inizializzata. In altre parole, prima del Fare inizializzazione dei delegati di classe alla sua superclasse, ogni proprietà definita dal Fare la classe deve avere un valore iniziale valido. Puoi verificarlo cambiando l'ordine delle affermazioni e controllando l'errore che si apre.

Lo stesso vale per il init? (coder :) metodo. Inizialmente inizializziamo il fatto proprietà prima di invocarlo init? (coder :) sulla superclasse.

Inizializzatori ed ereditarietà

Quando si tratta di ereditarietà e inizializzazione, ci sono alcune regole da tenere a mente. La regola per gli inizializzatori designati è semplice.

  • Un inizializzatore designato deve richiamare un inizializzatore designato dalla sua superclasse. Nel Fare classe, per esempio, il init? (coder :) il metodo invoca il init? (coder :) metodo della sua superclasse. Questo è anche indicato come delegando in su.

Le regole per gli inizializzatori di convenienza sono un po 'più complesse. Ci sono due regole da tenere a mente.

  • Un inizializzatore conveniente deve sempre richiamare un altro inizializzatore della classe in cui è definito. Nel Compito classe, per esempio, il dentro() metodo è un inizializzatore di convenienza e delega l'inizializzazione a un altro inizializzatore, init (nome :) nell'esempio. Questo è noto come delegando attraverso.
  • Anche se un inizializzatore conveniente non deve delegare l'inizializzazione a un inizializzatore designato, un inizializzatore conveniente deve chiamare un inizializzatore designato ad un certo punto. Questo è necessario per inizializzare completamente l'istanza che viene inizializzata.

Con entrambe le classi modello implementate, è ora di ridefinire il ViewController e AddItemViewController classi. Iniziamo con quest'ultimo.

2. Refactoring AddItemViewController

Passaggio 1: aggiorna il AddItemViewControllerDelegate Protocollo

Gli unici cambiamenti che dobbiamo apportare nel AddItemViewController classe sono legati al AddItemViewControllerDelegate protocollo. Nella dichiarazione del protocollo, cambia il tipo di didAddItem a partire dal Stringa a Fare, la classe del modello che abbiamo implementato in precedenza.

protocollo AddItemViewControllerDelegate func controller (_ controller: AddItemViewController, didAddItem: ToDo)

Passaggio 2: aggiorna il creare(_:) Azione

Ciò significa che dobbiamo anche aggiornare il creare(_:) azione in cui invochiamo il metodo delegato. Nell'implementazione aggiornata, creiamo a Fare istanza, passandola al metodo delegato.

@IBAction func create (_ sender: Any) se let name = textField.text // Crea elemento let item = ToDo (nome: name, done: false) // Notifica delegato delegato? .Controller (self, didAddItem: item )

3. Refactoring ViewController

Passaggio 1: aggiorna il elementi Proprietà

Il ViewController la lezione richiede un po 'più di lavoro. Per prima cosa dobbiamo cambiare il tipo di elementi proprietà a [Fare], una serie di Fare casi.

var items: [ToDo] = [] didSet (oldValue) let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItem

Passaggio 2: tabella Visualizza metodi di origine dati

Ciò significa anche che dobbiamo rifattorizzare alcuni altri metodi, come il tableView (_: cellForRowAt :) metodo mostrato di seguito. Perché il elementi la matrice ora contiene Fare le istanze, controllare se un elemento è contrassegnato come fatto è molto più semplice. Usiamo l'operatore condizionale ternario di Swift per aggiornare il tipo di accessorio della cella di visualizzazione tabella.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Fetch Item let item = items [indexPath.row] // Dequeue Cell let cella = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", per: indexPath // Configura la cella cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none return cell

Quando l'utente elimina un elemento, è necessario aggiornare solo il elementi proprietà rimuovendo il corrispondente Fare esempio. Questo si riflette nell'implementazione del tableView (_: commit: forRowAt :) metodo mostrato di seguito.

func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Aggiorna elementi items.remove (a: indexPath.row) // Aggiorna tabella Visualizza tableView.deleteRows (a : [indexPath], con: .right) // Salva stato saveItems ()

Passaggio 3: Metodi di delegazione di Visualizza tabella

L'aggiornamento dello stato di un oggetto quando l'utente tocca una riga viene gestito nel tableView (_: didSelectRowAt :) metodo. L'implementazione di questo UITableViewDelegate il metodo è molto più semplice grazie a Fare classe.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (at: indexPath, animated: true) // Fetch Item let item = items [indexPath.row] // Aggiorna elemento item.done =! item. done // Fetch Cell let cella = tableView.cellForRow (at: indexPath) // Aggiorna cella cell? .accessoryType = item.done? .checkmark: .none // Salva stato saveItems ()

Il corrispondente Fare l'istanza viene aggiornata e questa modifica viene riflessa dalla vista tabella. Per salvare lo stato, invochiamo saveItems () invece di saveCheckedItems ().

Passaggio 4: aggiungere i metodi di delegazione del controller Vista elemento

Perché abbiamo aggiornato il AddItemViewControllerDelegate protocollo, abbiamo anche bisogno di aggiornare il ViewControllerL'implementazione di questo protocollo. Il cambiamento, tuttavia, è semplice. Abbiamo solo bisogno di aggiornare la firma del metodo.

func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Aggiorna origine dati items.append (didAddItem) // Salva stato saveItems () // Ricarica tabella Visualizza tableView.reloadData () // Ignora Aggiungi elemento Visualizza Controller ignora ( animato: vero)

Passaggio 5: Salva elementi

Il pathForItems () Metodo

Anziché archiviare gli elementi nel database dei valori predefiniti dell'utente, li memorizzeremo nella directory dei documenti dell'applicazione. Prima di aggiornare il loadItems () e saveItems () metodi, stiamo per implementare un metodo di supporto chiamato pathForItems (). Il metodo è privato e restituisce un percorso, la posizione degli elementi nella directory dei documenti.

private func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, let url = URL (string: documentsDirectory) else fatalError ("Directory documenti non trovata") return url. appendingPathComponent ("items"). path

Per prima cosa richiamiamo il percorso della directory dei documenti nella sandbox dell'applicazione richiamando NSSearchPathForDirectoriesInDomains (_: _: _ :). Poiché questo metodo restituisce una serie di stringhe, prendiamo il primo elemento.

Si noti che usiamo a guardia dichiarazione per assicurarsi che il valore restituito da NSSearchPathForDirectoriesInDomains (_: _: _ :) è valido. Si genera un errore irreversibile se questa operazione non riesce. Questo termina immediatamente l'applicazione. Perché lo facciamo? Se il sistema operativo non è in grado di fornirci il percorso per la directory dei documenti, abbiamo problemi più grandi di cui preoccuparci.

Il valore da cui torniamo pathForItems () è composto dal percorso della directory dei documenti con la stringa "elementi" aggiunto ad esso.

Il loadItems () Metodo

Il metodo loadItems cambia un po '. Per prima cosa memorizziamo il risultato di pathForItems () in una costante, sentiero. Quindi, annulliamo l'archiviazione dell'oggetto archiviato in quel percorso e lo sottoponilo a una matrice opzionale di Fare le istanze. Utilizziamo l'associazione facoltativa per scartare l'opzionale e assegnarlo a una costante, elementi. Nel Se clausola, assegniamo il valore memorizzato in elementi al elementi proprietà.

private func loadItems () let path = pathForItems () se let items = NSKeyedUnarchiver.unarchiveObject (withFile: path) as? [ToDo] self.items = items

Il saveItems () Metodo

Il saveItems () il metodo è breve e semplice Memorizziamo il risultato di pathForItems () in una costante, sentiero, e invocare archiveRootObject (_: tofile :) sopra NSKeyedArchiver, passando nel elementi proprietà e sentiero. Stampiamo il risultato dell'operazione sulla console.

private func saveItems () let path = pathForItems () se NSKeyedArchiver.archiveRootObject (self.items, toFile: path) print ("Successfully Saved") else print ("Salvataggio non riuscito")

Passaggio 6: ripulire

Terminiamo con la parte divertente, eliminando il codice. Inizia rimuovendo il CheckedItems proprietà al vertice poiché non ne abbiamo più bisogno. Di conseguenza, possiamo anche rimuovere il loadCheckedItems () e saveCheckedItems () metodi e ogni riferimento a questi metodi nel ViewController classe.

Costruisci ed esegui l'applicazione per vedere se tutto funziona ancora. Il modello dati rende il codice dell'applicazione molto più semplice e affidabile. Grazie al Fare classe, la gestione degli articoli nella nostra lista è ora più facile e meno soggetta a errori.

Conclusione

In questa lezione, abbiamo rifatto il modello dei dati della nostra applicazione. Hai imparato di più sulla programmazione orientata agli oggetti e l'ereditarietà. L'inizializzazione dell'istanza è un concetto importante in Swift, quindi assicurati di capire cosa abbiamo trattato in questa lezione. Puoi leggere ulteriori informazioni sulla delega di inizializzazione e inizializzazione in Swift Programming Language.

Nel frattempo, dai uno sguardo ad alcuni dei nostri altri corsi e tutorial sullo sviluppo di iOS in lingua Swift!