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.
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.
Compito
ClasseIniziamo 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?
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.
NSCoding
ProtocolloL'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.
Fare
ClasseCon 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.
Quando si tratta di ereditarietà e inizializzazione, ci sono alcune regole da tenere a mente. La regola per gli inizializzatori designati è semplice.
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.
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.Con entrambe le classi modello implementate, è ora di ridefinire il ViewController
e AddItemViewController
classi. Iniziamo con quest'ultimo.
AddItemViewController
AddItemViewControllerDelegate
ProtocolloGli 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)
creare(_:)
AzioneCiò 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 )
ViewController
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
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 ()
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 ()
.
Perché abbiamo aggiornato il AddItemViewControllerDelegate
protocollo, abbiamo anche bisogno di aggiornare il ViewController
L'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)
pathForItems ()
MetodoAnziché 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.
loadItems ()
MetodoIl 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
saveItems ()
MetodoIl 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")
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.
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!