En la lección anterior de Swift From Scratch, creamos una aplicación funcional de tareas. Sin embargo, el modelo de datos podría usar algo de amor. En esta lección final, vamos a refactorizar el modelo de datos implementando una clase de modelo personalizado.
El modelo de datos que vamos a implementar incluye dos clases, una Tarea
clase y un Que hacer
clase que hereda de la Tarea
clase. Mientras creamos e implementamos estas clases de modelo, continuaremos nuestra exploración de la programación orientada a objetos en Swift. En esta lección, ampliaremos la inicialización de las instancias de clase y el papel que juega la herencia durante la inicialización..
Tarea
ClaseComencemos con la implementación del Tarea
clase. Crea un nuevo archivo Swift seleccionando Nuevo> Archivo ... de Xcode's Expediente menú. Escoger Archivo rápido desde el iOS> Fuente sección. Nombra el archivo Cambio de tarea y golpear Crear.
La implementación básica es corta y simple. los Tarea
clase hereda de NSObject
, definido en el Fundación Marco, y tiene una propiedad variable. nombre
de tipo Cuerda
. La clase define dos inicializadores., en eso()
y init (nombre :)
. Hay algunos detalles que podrían hacerte tropezar, así que déjame explicarte lo que está pasando.
importar clase de la Fundación Tarea: NSObject var nombre: Cadena anular conveniencia init () self.init (name: "New Task") init (name: String) self.name = name
Porque el en eso()
método también se define en el NSObject
clase, tenemos que prefijar el inicializador con el anular
palabra clave. Cubrimos los métodos de anulación anteriormente en esta serie. En el en eso()
método, invocamos el init (nombre :)
método, pasar "Nueva tarea"
como el valor para el nombre
parámetro.
los init (nombre :)
El método es otro inicializador, aceptando un solo parámetro. nombre
de tipo Cuerda
. En este inicializador, el valor del nombre
parámetro asignado a la nombre
propiedad. Esto es bastante fácil de entender. Derecha?
¿Qué pasa con el conveniencia
palabra clave prefijando el en eso()
¿método? Las clases pueden tener dos tipos de inicializadores., designada inicializadores y conveniencia Inicializadores. Los inicializadores de conveniencia tienen el prefijo conveniencia
palabra clave, lo que implica que init (nombre :)
es un inicializador designado. ¿Porqué es eso? ¿Cuál es la diferencia entre los inicializadores designados y convenientes??
Inicializadores designados Inicialice completamente una instancia de una clase, lo que significa que cada propiedad de la instancia tiene un valor inicial después de la inicialización. Mirando a la Tarea
clase, por ejemplo, vemos que la nombre
propiedad se establece con el valor de la nombre
parámetro de la init (nombre :)
inicializador El resultado después de la inicialización es completamente inicializado. Tarea
ejemplo.
Inicializadores de conveniencia, sin embargo, confíe en un inicializador designado para crear una instancia completamente inicializada de la clase. Por eso el en eso()
inicializador de la Tarea
clase invoca el init (nombre :)
Inicializador en su implementación. Esto se conoce como delegación inicializadora. los en eso()
el inicializador delega la inicialización a un inicializador designado para crear una instancia completamente inicializada del Tarea
clase.
Inicializadores de conveniencia son opcionales. No todas las clases tienen un inicializador de conveniencia. Se requieren inicializadores designados, y una clase debe tener al menos un inicializador designado para crear una instancia completamente inicializada de sí misma.
NSCoding
ProtocoloLa implementación de la Tarea
Sin embargo, la clase no está completa. Más adelante en esta lección, escribiremos una serie de Que hacer
instancias al disco. Esto solo es posible si las instancias de Que hacer
La clase puede ser codificada y decodificada.
Sin embargo, no se preocupe, esto no es ciencia espacial. Solo necesitamos hacer el Tarea
y Que hacer
las clases se ajustan a la NSCoding
protocolo. Por eso el Tarea
clase hereda de la NSObject
clase desde el NSCoding
el protocolo solo puede ser implementado por clases heredadas directa o indirectamente de NSObject
. Como el NSObject
clase, la NSCoding
protocolo se define en el Fundación marco de referencia.
Adoptar un protocolo es algo que ya hemos cubierto en esta serie, pero hay algunos errores que quiero señalar. Vamos a empezar diciéndole al compilador que el Tarea
clase se ajusta a la NSCoding
protocolo.
Importar clase de Foundation Tarea: NSObject, NSCoding var name: String…
A continuación, necesitamos implementar los dos métodos declarados en el NSCoding
protocolo, init? (codificador :)
y codificar (con :)
. La implementación es sencilla si está familiarizado con el NSCoding
protocolo.
Importar clase de la Fundación Tarea: NSObject, NSCoding var nombre: String @objc required init? (coder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "name") as! Cadena @objc func encode (con aCoder: NSCoder) aCoder.encode (name, forKey: "name") conveniencia anular init () self.init (name: "New Task") init (name: String) self.name = name
los init? (codificador :)
el inicializador es un inicializador designado que inicializa un Tarea
ejemplo. Aunque implementamos el init? (codificador :)
método para ajustarse a la NSCoding
protocolo, nunca necesitarás invocar este método directamente. Lo mismo es cierto para codificar (con :)
, que codifica una instancia de la Tarea
clase.
los necesario
palabra clave prefijando el init? (codificador :)
método indica que cada subclase de la Tarea
La clase necesita implementar este método. los necesario
la palabra clave solo se aplica a los inicializadores, por lo que no necesitamos agregarla a la codificar (con :)
método.
Antes de continuar, necesitamos hablar de @objc
atributo. Porque el NSCoding
protocolo es un protocolo de Objective-C, conformidad del protocolo Solo se puede verificar agregando el @objc
atributo. En Swift, no existe tal cosa como la conformidad del protocolo o los métodos de protocolo opcionales. En otras palabras, si una clase se adhiere a un protocolo particular, el compilador verifica y espera que todos los métodos del protocolo se implementen..
Que hacer
ClaseCon el Tarea
clase implementada, es hora de implementar el Que hacer
clase. Crea un nuevo archivo Swift y llámalo ToDo.swift. Echemos un vistazo a la implementación de la Que hacer
clase.
importar clase de base ToDo: Tarea var done: Bool @objc required init? (coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") super.init (coder: aDecoder) @objc override func codificar (con aCoder: NSCoder) aCoder.encode (hecho, forKey: "done") super.encode (con: aCoder) init (nombre: String, done: Bool) self.done = hecho super.init (nombre : nombre)
los Que hacer
clase hereda de la Tarea
Clase y declara una propiedad variable. hecho
de tipo Bool
. Además de los dos métodos requeridos de la NSCoding
protocolo que hereda de la Tarea
clase, también declara un inicializador designado, init (nombre: hecho :)
.
Como en Objective-C, el súper
palabra clave se refiere a la superclase, la Tarea
clase en este ejemplo. Hay un detalle importante que merece atención. Antes de invocar el init (nombre :)
método en la superclase, cada propiedad declarada por la Que hacer
clase necesita ser inicializada En otras palabras, antes de la Que hacer
La clase delega la inicialización a su superclase, cada propiedad definida por el Que hacer
La clase necesita tener un valor inicial válido. Puede verificar esto cambiando el orden de las declaraciones e inspeccionando el error que aparece.
Lo mismo se aplica a la init? (codificador :)
método. Primero inicializamos hecho
propiedad antes de invocar init? (codificador :)
en la superclase.
Cuando se trata de la herencia y la inicialización, hay algunas reglas a tener en cuenta. La regla para los inicializadores designados es simple..
Que hacer
clase, por ejemplo, la init? (codificador :)
método invoca el init? (codificador :)
Método de su superclase. Esto también se conoce como delegar hasta.Las reglas para los inicializadores de conveniencia son un poco más complejas. Hay dos reglas a tener en cuenta.
Tarea
clase, por ejemplo, la en eso()
El método es un inicializador conveniente y la inicialización de los delegados a otro inicializador., init (nombre :)
en el ejemplo Esto se conoce como delegar a través de.Con ambas clases modelo implementadas, es hora de refactorizar el ViewController
y AddItemViewController
clases Empecemos por lo último..
AddItemViewController
AddItemViewControllerDelegate
ProtocoloLos únicos cambios que necesitamos hacer en el AddItemViewController
clase están relacionados con la AddItemViewControllerDelegate
protocolo. En la declaración de protocolo, cambie el tipo de didAddItem
desde Cuerda
a Que hacer
, la clase modelo que implementamos anteriormente.
protocol AddItemViewControllerDelegate func controller (_ controller: AddItemViewController, didAddItem: ToDo)
crear(_:)
AcciónEsto significa que también necesitamos actualizar el crear(_:)
Acción en la que invocamos el método delegado. En la implementación actualizada, creamos un Que hacer
instancia, pasándolo al método delegado.
@IBAction func create (_ sender: Any) if let name = textField.text // Create Item let item = ToDo (nombre: nombre, hecho: falso) // Notificar al delegado delegado? .Controller (self, didAddItem: item )
ViewController
artículos
Propiedadlos ViewController
La clase requiere un poco más de trabajo. Primero necesitamos cambiar el tipo de artículos
propiedad a [Que hacer]
, una serie de Que hacer
instancias.
var items: [ToDo] = [] didSet (oldValue) let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems
Esto también significa que necesitamos refactorizar algunos otros métodos, como el tableView (_: cellForRowAt :)
método que se muestra a continuación. Porque el artículos
matriz ahora contiene Que hacer
instancias, verificar si un elemento está marcado como hecho es mucho más simple. Utilizamos el operador condicional ternario de Swift para actualizar el tipo de accesorio de la celda de vista de tabla.
func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Fetch Item let item = items [indexPath.row] // Dequeue Cell let cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", para: indexPath ) // Configurar Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none devolver celda
Cuando el usuario elimina un elemento, solo necesitamos actualizar el artículos
propiedad mediante la eliminación de la correspondiente Que hacer
ejemplo. Esto se refleja en la implementación del tableView (_: commit: forRowAt :)
método mostrado abajo.
func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Update Items items.remove (at: indexPath.row) // Update Table View tableView.deleteRows (at : [indexPath], con: .right) // Save State saveItems ()
La actualización del estado de un elemento cuando el usuario toca una fila se maneja en el tableView (_: didSelectRowAt :)
método. La implementación de este UITableViewDelegate
El método es mucho más sencillo gracias a la Que hacer
clase.
func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (at: indexPath, animated: true) // Fetch Item let item = items [indexPath.row] // Actualizar item item.done =! item. hecho // Recuperar celda dejar celda = tableView.cellForRow (at: indexPath) // Actualizar celda? .accessoryType = item.done? .checkmark: .none // Save State saveItems ()
El correspondiente Que hacer
La instancia se actualiza y este cambio se refleja en la vista de tabla. Para salvar el estado, invocamos. saveItems ()
en lugar de saveCheckedItems ()
.
Porque actualizamos el AddItemViewControllerDelegate
protocolo, también necesitamos actualizar el ViewController
Implementación de este protocolo. El cambio, sin embargo, es simple. Solo necesitamos actualizar la firma del método..
func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Actualizar la fuente de datos items.append (didAddItem) // Guardar estado saveItems () // Volver a cargar la tabla tableView.reloadData () // Descartar Add Item View Controller despide ( animado: verdadero)
pathForItems ()
MétodoEn lugar de almacenar los elementos en la base de datos predeterminada del usuario, los almacenaremos en el directorio de documentos de la aplicación. Antes de actualizar el loadItems ()
y saveItems ()
métodos, vamos a implementar un método auxiliar llamado pathForItems ()
. El método es privado y devuelve una ruta, la ubicación de los elementos en el directorio de documentos.
private func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, let url = URL (string: documentsDirectory) else fatalError ("Documents Directory Not Found")) return url. appendingPathComponent ("items"). path
Primero obtenemos la ruta al directorio de documentos en el entorno limitado de la aplicación invocando NSSearchPathForDirectoriesInDomains (_: _: _ :)
. Debido a que este método devuelve una matriz de cadenas, tomamos el primer elemento.
Tenga en cuenta que usamos un Guardia
declaración para asegurarse de que el valor devuelto por NSSearchPathForDirectoriesInDomains (_: _: _ :)
es válida. Lanzamos un error fatal si esta operación falla. Esto termina inmediatamente la aplicación. ¿Por qué hacemos esto? Si el sistema operativo no puede entregarnos la ruta al directorio de documentos, tenemos más problemas de los que preocuparnos..
El valor del que volvemos pathForItems ()
se compone de la ruta al directorio de documentos con la cadena "artículos"
anexado a ello.
loadItems ()
MétodoEl método loadItems cambia bastante. Primero almacenamos el resultado de pathForItems ()
en una constante, camino
. Luego, desarchivamos el objeto archivado en esa ruta y lo convertimos a una matriz opcional de Que hacer
instancias. Utilizamos el enlace opcional para desenvolver el opcional y asignarlo a una constante, artículos
. En el Si
cláusula, le asignamos el valor almacenado en artículos
al artículos
propiedad.
private function loadItems () let path = pathForItems () si let items = NSKeyedUnarchiver.unarchiveObject (withFile: path) como? [ToDo] self.items = items
saveItems ()
Métodolos saveItems ()
El método es corto y simple. Almacenamos el resultado de pathForItems ()
en una constante, camino
, y invocar archiveRootObject (_: toFile :)
en NSKeyedArchiver
, pasando en el artículos
propiedad y camino
. Imprimimos el resultado de la operación a la consola..
private func saveItems () let path = pathForItems () if NSKeyedArchiver.archiveRootObject (self.items, toFile: path) print ("Successfully Saved") else print ("Saving Failed")
Acabemos con la parte divertida, borrando código. Comience por quitar el artículos revisados
Propiedad en la parte superior ya que ya no la necesitamos. Como resultado, también podemos eliminar el loadCheckedItems ()
y saveCheckedItems ()
métodos, y toda referencia a estos métodos en el ViewController
clase.
Construye y ejecuta la aplicación para ver si todo sigue funcionando. El modelo de datos hace que el código de la aplicación sea mucho más simple y confiable. Gracias a Que hacer
clase, administrar los elementos de nuestra lista es ahora mucho más fácil y menos propenso a errores.
En esta lección, hemos refactorizado el modelo de datos de nuestra aplicación. Aprendiste más sobre la programación orientada a objetos y la herencia. La inicialización de instancias es un concepto importante en Swift, así que asegúrese de entender lo que hemos cubierto en esta lección. Puede leer más sobre la delegación de inicialización y inicialización en The Swift Programming Language.
Mientras tanto, echa un vistazo a algunos de nuestros otros cursos y tutoriales sobre el desarrollo de Swift en iOS.!